diff options
author | Aleksander Morgado <aleksandermj@chromium.org> | 2022-12-08 13:37:55 +0000 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2023-01-03 13:56:25 +0000 |
commit | e14b904cbd6816cb0227d519d308ae71ddaf6e07 (patch) | |
tree | 4997ab68cc606fdf4d72a571e821cec0c8df42ef /src/plugins | |
parent | 072d7ac9065f444e83b390a1e2af5471ac0d48f6 (diff) |
build: move plugins directory to src/plugins
We are going to allow including the plugin sources built within the
ModemManager daemon binary; moving the sources within the daemon
sources directory makes it easier.
Diffstat (limited to 'src/plugins')
315 files changed, 85477 insertions, 0 deletions
diff --git a/src/plugins/README.txt b/src/plugins/README.txt new file mode 100644 index 00000000..ff36dc41 --- /dev/null +++ b/src/plugins/README.txt @@ -0,0 +1,160 @@ + +The following list shows the relationship among the different plugins provided +by default by ModemManager. For each of the plugin types, the list of modem +objects created by the plugin is given. + + * Altair: + ** MMBroadbandModemAltairLte + + * Anydata: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemAnydata + + * Cinterion: + ** MMBroadbandModemQmiCinterion + ** MMBroadbandModemCinterion + + * Dell: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemFoxconnT77w968 (from the Foxconn utils) + ** MMBroadbandModemMbimXmm (from the XMM utils) + ** MMBroadbandModemMbim (generic) + ** MMBroadbandModemNovatel (from the Novatel utils) + ** MMBroadbandModemSierra (from the Sierra Legacy utils) + ** MMBroadbandModemTelit (from the Telit utils) + ** MMBroadbandModemXmm (from the XMM utils) + ** MMBroadbandModem (generic) + + * D-Link: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModem (generic) + + * Fibocom: + ** MMBroadbandModemMbimXmm (from the XMM utils) + ** MMBroadbandModemMbim (generic) + ** MMBroadbandModemXmm (from the XMM utils) + ** MMBroadbandModem (generic) + + * Foxconn: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemFoxconnT77w968 + ** MMBroadbandModemMbim (generic) + ** MMBroadbandModem (generic) + + * Generic: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemMbim (generic) + ** MMBroadbandModem (generic) + + * Gosuncn: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemMbim (generic) + ** MMBroadbandModem (generic) + + * Haier: + ** MMBroadbandModem (generic) + + * Huawei: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemMbim (generic) + ** MMBroadbandModemHuawei + + * Icera (no explicit plugin): + ** MMBroadbandModemIcera + + * Iridium: + ** MMBroadbandModemIridium + + * Linktop: + ** MMBroadbandModemLinktop + + * Longcheer: + ** MMBroadbandModemLongcheer + + * MBM: + ** MMBroadbandModemMbim (generic) + ** MMBroadbandModemMbm + + * Motorola: + ** MMBroadbandModemMotorola + + * Mtk: + ** MMBroadbandModemMtk + + * Nokia: + ** MMBroadbandModemNokia + + * Nokia Icera: + ** MMBroadbandModemIcera (from the Icera utils) + + * Novatel: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemNovatel + + * Novatel LTE: + ** MMBroadbandModemNovatelLte + + * Option: + ** MMBroadbandModemOption + + * Option HSO: + ** MMBroadbandModemHso + + * Pantech: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemPantech + + * Quectel: + ** MMBroadbandModemQmiQuectel + ** MMBroadbandModemQuectel + + * Samsung: + ** MMBroadbandModemSamsung (subclassed from the Icera utils) + + * Sierra Legacy: + ** MMBroadbandModemSierraIcera (subclassed from the Icera utils) + ** MMBroadbandModemSierra + + * Sierra: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemMbim (generic) + ** MMBroadbandModem (generic) + + * Simtech: + ** MMBroadbandModemQmiSimtech + ** MMBroadbandModemSimtech + + * Telit: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemMbimTelit + ** MMBroadbandModemTelit + + * Thuraya + ** MMBroadbandModemThuraya + + * TP-Link: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModem (generic) + + * u-blox: + ** MMBroadbandModemUblox + + * via: + ** MMBroadbandModemVia + + * wavecom: + ** MMBroadbandModemWavecom + + * x22x: + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemX22x + + * XMM (no explicit plugin): + ** MMBroadbandModemMbimXmm + ** MMBroadbandModemXmm + + * ZTE + ** MMBroadbandModemQmi (generic) + ** MMBroadbandModemMbim (generic) + ** MMBroadbandModemZteIcera (subclassed from the Icera utils) + ** MMBroadbandModemZte diff --git a/src/plugins/altair/mm-broadband-bearer-altair-lte.c b/src/plugins/altair/mm-broadband-bearer-altair-lte.c new file mode 100644 index 00000000..812b04d8 --- /dev/null +++ b/src/plugins/altair/mm-broadband-bearer-altair-lte.c @@ -0,0 +1,370 @@ +/* -*- 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) 2013 Altair Semiconductor + * + * Author: Ori Inbar <ori.inbar@altair-semi.com> + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-altair-lte.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-log.h" +#include "mm-modem-helpers.h" + +#define CONNECTION_CHECK_TIMEOUT_SEC 5 +#define STATCM_TAG "%STATCM:" + +G_DEFINE_TYPE (MMBroadbandBearerAltairLte, mm_broadband_bearer_altair_lte, MM_TYPE_BROADBAND_BEARER); + +/*****************************************************************************/ +/* 3GPP Connect sequence */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + MMPort *data; +} DetailedConnectContext; + +static void +detailed_connect_context_free (DetailedConnectContext *ctx) +{ + g_object_unref (ctx->data); + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_free (ctx); +} + +static MMBearerConnectResult * +connect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +connect_3gpp_connect_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + DetailedConnectContext *ctx; + const gchar *result; + GError *error = NULL; + MMBearerIpConfig *config; + + result = mm_base_modem_at_command_full_finish (modem, res, &error); + if (!result) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + config = mm_bearer_ip_config_new (); + + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP); + + /* Set operation result */ + g_task_return_pointer ( + task, + mm_bearer_connect_result_new (ctx->data, config, config), + (GDestroyNotify)mm_bearer_connect_result_unref); + g_object_unref (task); + + g_object_unref (config); +} + +static void +connect_3gpp_apnsettings_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + DetailedConnectContext *ctx; + const gchar *result; + GError *error = NULL; + + result = mm_base_modem_at_command_full_finish (modem, res, &error); + if (!result) { + g_prefix_error (&error, "setting APN failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "%DPDNACT=1", + MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, /* timeout */ + FALSE, /* allow_cached */ + FALSE, /* is_raw */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)connect_3gpp_connect_ready, + task); /* user_data */ +} + +static void +connect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DetailedConnectContext *ctx; + gchar *command, *apn; + MMBearerProperties *config; + MMModem3gppRegistrationState registration_state; + MMPort *data; + GTask *task; + + /* There is a known firmware bug that can leave the modem unusable if a + * connect attempt is made when out of coverage. So, fail without trying. + */ + g_object_get (modem, + MM_IFACE_MODEM_3GPP_REGISTRATION_STATE, ®istration_state, + NULL); + if (registration_state == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN) { + g_task_report_new_error (self, + callback, + user_data, + connect_3gpp, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK, + "Out of coverage, can't connect."); + return; + } + + /* Don't allow a connect while we detach from the network to process SIM + * refresh. + * */ + if (mm_broadband_modem_altair_lte_is_sim_refresh_detach_in_progress (modem)) { + mm_obj_dbg (self, "detached from network to process SIM refresh, failing connect request"); + g_task_report_new_error (self, + callback, + user_data, + connect_3gpp, + MM_CORE_ERROR, + MM_CORE_ERROR_RETRY, + "Detached from network to process SIM refresh, can't connect."); + return; + } + + data = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET); + if (!data) { + g_task_report_new_error (self, + callback, + user_data, + connect_3gpp, + MM_CORE_ERROR, + MM_CORE_ERROR_CONNECTED, + "Couldn't connect: no available net port available"); + return; + } + + ctx = g_new0 (DetailedConnectContext, 1); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + ctx->data = g_object_ref (data); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_connect_context_free); + + config = mm_base_bearer_peek_config (MM_BASE_BEARER (self)); + apn = mm_port_serial_at_quote_string (mm_bearer_properties_get_apn (config)); + command = g_strdup_printf ("%%APNN=%s", apn); + g_free (apn); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 10, /* timeout */ + FALSE, /* allow_cached */ + FALSE, /* is_raw */ + cancellable, + (GAsyncReadyCallback)connect_3gpp_apnsettings_ready, + task); /* user_data */ + g_free (command); +} + +/*****************************************************************************/ +/* 3GPP Disconnect sequence */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + MMPort *data; +} DetailedDisconnectContext; + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +detailed_disconnect_context_free (DetailedDisconnectContext *ctx) +{ + g_object_unref (ctx->data); + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_free (ctx); +} + +static void +disconnect_3gpp_check_status (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + + const gchar *result; + GError *error = NULL; + + result = mm_base_modem_at_command_full_finish (modem, res, &error); + if (!result) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +disconnect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DetailedDisconnectContext *ctx; + MMModem3gppRegistrationState registration_state; + GTask *task; + + /* There is a known firmware bug that can leave the modem unusable if a + * disconnect attempt is made when out of coverage. So, fail without trying. + */ + g_object_get (modem, + MM_IFACE_MODEM_3GPP_REGISTRATION_STATE, ®istration_state, + NULL); + if (registration_state == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN) { + g_task_report_new_error (self, + callback, + user_data, + disconnect_3gpp, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK, + "Out of coverage, can't disconnect."); + return; + } + + ctx = g_new0 (DetailedDisconnectContext, 1); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + ctx->data = g_object_ref (data); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_disconnect_context_free); + + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "%DPDNACT=0", + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, /* timeout */ + FALSE, /* allow_cached */ + FALSE, /* is_raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)disconnect_3gpp_check_status, + task); /* user_data */ +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_broadband_bearer_altair_lte_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *bearer; + GObject *source; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_altair_lte_new (MMBroadbandModemAltairLte *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + NULL); +} + +static void +mm_broadband_bearer_altair_lte_init (MMBroadbandBearerAltairLte *self) +{ + +} + +static void +mm_broadband_bearer_altair_lte_class_init (MMBroadbandBearerAltairLteClass *klass) +{ + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + base_bearer_class->load_connection_status = NULL; + base_bearer_class->load_connection_status_finish = NULL; +#if defined WITH_SUSPEND_RESUME + base_bearer_class->reload_connection_status = NULL; + base_bearer_class->reload_connection_status_finish = NULL; +#endif + + broadband_bearer_class->connect_3gpp = connect_3gpp; + broadband_bearer_class->connect_3gpp_finish = connect_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; +} diff --git a/src/plugins/altair/mm-broadband-bearer-altair-lte.h b/src/plugins/altair/mm-broadband-bearer-altair-lte.h new file mode 100644 index 00000000..907c7743 --- /dev/null +++ b/src/plugins/altair/mm-broadband-bearer-altair-lte.h @@ -0,0 +1,59 @@ +/* -*- 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) 2013 Altair Semiconductor + * + * Author: Ori Inbar <ori.inbar@altair-semi.com> + */ + +#ifndef MM_BROADBAND_BEARER_ALTAIR_LTE_H +#define MM_BROADBAND_BEARER_ALTAIR_LTE_H + +#include <glib.h> +#include <glib-object.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-altair-lte.h" + +#define MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE (mm_broadband_bearer_altair_lte_get_type ()) +#define MM_BROADBAND_BEARER_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE, MMBroadbandBearerAltairLte)) +#define MM_BROADBAND_BEARER_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE, MMBroadbandBearerAltairLteClass)) +#define MM_IS_BROADBAND_BEARER_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE)) +#define MM_IS_BROADBAND_BEARER_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE)) +#define MM_BROADBAND_BEARER_ALTAIR_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE, MMBroadbandBearerAltairLteClass)) + +typedef struct _MMBroadbandBearerAltairLte MMBroadbandBearerAltairLte; +typedef struct _MMBroadbandBearerAltairLteClass MMBroadbandBearerAltairLteClass; + +struct _MMBroadbandBearerAltairLte { + MMBroadbandBearer parent; +}; + +struct _MMBroadbandBearerAltairLteClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_altair_lte_get_type (void); + +/* Default 3GPP bearer creation implementation */ +void mm_broadband_bearer_altair_lte_new (MMBroadbandModemAltairLte *modem, + MMBearerProperties *properties, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_altair_lte_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_BROADBAND_BEARER_ALTAIR_LTE_H */ diff --git a/src/plugins/altair/mm-broadband-modem-altair-lte.c b/src/plugins/altair/mm-broadband-modem-altair-lte.c new file mode 100644 index 00000000..837d57cc --- /dev/null +++ b/src/plugins/altair/mm-broadband-modem-altair-lte.c @@ -0,0 +1,1313 @@ +/* -*- 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) 2013 Altair Semiconductor + * + * Author: Ori Inbar <ori.inbar@altair-semi.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-altair-lte.h" +#include "mm-broadband-modem-altair-lte.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-3gpp-ussd.h" +#include "mm-iface-modem-messaging.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-altair-lte.h" +#include "mm-serial-parsers.h" +#include "mm-bearer-list.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface); +static void iface_modem_messaging_init (MMIfaceModemMessaging *iface); + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemAltairLte, mm_broadband_modem_altair_lte, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_USSD, iface_modem_3gpp_ussd_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)) + +struct _MMBroadbandModemAltairLtePrivate { + /* Regex for SIM refresh notifications */ + GRegex *sim_refresh_regex; + /* Timer that goes off 10s after the last SIM refresh notification. + * This indicates that there are no more SIM refreshes and we should + * reregister the device.*/ + guint sim_refresh_timer_id; + /* Flag indicating that we are detaching from the network to process SIM + * refresh. This is used to prevent connect requests while we're in this + * state.*/ + gboolean sim_refresh_detach_in_progress; + /* Regex for bearer related notifications */ + GRegex *statcm_regex; + /* Regex for PCO notifications */ + GRegex *pcoinfo_regex; + + GList *pco_list; +}; + +static MMIfaceModem3gpp *iface_modem_3gpp_parent; + + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=4", + 20, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_altair_lte_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + g_object_unref (task); +} + +static void +modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* We just create a MMBroadbandBearer */ + mm_broadband_bearer_altair_lte_new (MM_BROADBAND_MODEM_ALTAIR_LTE (self), + properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_new_ready, + task); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_unlock_retries_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + gint pin1, puk1, pin2, puk2; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + response = mm_strip_tag (response, "%CPININFO:"); + if (sscanf (response, " %d, %d, %d, %d", &pin1, &puk1, &pin2, &puk2) == 4) { + MMUnlockRetries *retries; + + retries = mm_unlock_retries_new (); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2); + g_task_return_pointer (task, retries, g_object_unref); + } else { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid unlock retries response: '%s'", + response); + } + g_object_unref (task); +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "%CPININFO", + 3, + FALSE, + (GAsyncReadyCallback)load_unlock_retries_ready, + task); +} + +/*****************************************************************************/ +/* Load current capabilities (Modem interface) */ + +static MMModemCapability +load_current_capabilities_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_CAPABILITY_NONE; + } + return (MMModemCapability)value; +} + +static void +load_current_capabilities (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + /* This modem is LTE only.*/ + g_task_return_int (task, MM_MODEM_CAPABILITY_LTE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Load supported bands (Modem interface) */ + +static GArray * +load_supported_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +#define BANDCAP_TAG "%BANDCAP: " + +static void +load_supported_bands_done (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GArray *bands; + const gchar *response; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* + * Response is "%BANDCAP: <band>,[<band>...]" + */ + response = mm_strip_tag (response, BANDCAP_TAG); + + bands = mm_altair_parse_bands_response (response); + if (!bands) { + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse supported bands response"); + g_object_unref (task); + return; + } + + g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); + g_object_unref (task); +} + +static void +load_supported_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "%BANDCAP=", + 3, + FALSE, + (GAsyncReadyCallback)load_supported_bands_done, + task); +} + +/*****************************************************************************/ +/* Load current bands (Modem interface) */ + +static GArray * +load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +#define CFGBANDS_TAG "Bands: " + +static void +load_current_bands_done (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GArray *bands; + const gchar *response; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* + * Response is "Bands: <band>,[<band>...]" + */ + response = mm_strip_tag (response, CFGBANDS_TAG); + + bands = mm_altair_parse_bands_response (response); + if (!bands) { + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse current bands response"); + g_object_unref (task); + return; + } + + g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); + g_object_unref (task); +} + +static void +load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "%GETCFG=\"BAND\"", + 3, + FALSE, + (GAsyncReadyCallback)load_current_bands_done, + task); +} + +/*****************************************************************************/ +/* Reset (Modem interface) */ + +static gboolean +reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "ATZ", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Run registration checks (3GPP interface) */ + +static gboolean +modem_3gpp_run_registration_checks_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +simulate_unprovisioned_subscription_pco_update (MMBroadbandModemAltairLte *self) +{ + MMPco *pco; + + /* Simulate a %PCOINFO notification issued to the IMS PDN that indicates an + * unprovisioned Verizon SIM. See mm_altair_parse_vendor_pco_info() for the + * detailed format of a %PCOINFO response. + * + * 1,FF00,13018405 is constructed as follows: + * + * 1: CID for IMS PDN + * FF 00: Container ID for the Verizon-specific PCO content + * 13 01 84: Binary coded decimal representation of Verizon MCC/MNC 311/084 + * 05: Value indicating an unprovisioned SIM + */ + pco = mm_altair_parse_vendor_pco_info ("%PCOINFO: 1,FF00,13018405", NULL); + g_assert (pco != NULL); + self->priv->pco_list = mm_pco_list_add (self->priv->pco_list, pco); + mm_iface_modem_3gpp_update_pco_list (MM_IFACE_MODEM_3GPP (self), self->priv->pco_list); + g_object_unref (pco); +} + +static void +run_registration_checks_subscription_state_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *at_response; + gchar *ceer_response; + + /* If the AT+CEER command fails, or we fail to obtain a valid result, we + * ignore the error. This allows the registration attempt to continue. + * So, the async response from this function is *always* True. + */ + + at_response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!at_response) { + g_assert (error); + mm_obj_warn (self, "AT+CEER failed: %s", error->message); + g_error_free (error); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + ceer_response = mm_altair_parse_ceer_response (at_response, &error); + if (!ceer_response) { + g_assert (error); + mm_obj_warn (self, "Failed to parse AT+CEER response: %s", error->message); + g_error_free (error); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (g_strcmp0 ("EPS_AND_NON_EPS_SERVICES_NOT_ALLOWED", ceer_response) == 0) { + mm_obj_dbg (self, "registration failed due to unprovisioned SIM"); + simulate_unprovisioned_subscription_pco_update (MM_BROADBAND_MODEM_ALTAIR_LTE (self)); + } else { + mm_obj_dbg (self, "failed to find a better reason for registration failure"); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + g_free (ceer_response); +} + +static void +run_registration_checks_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + gboolean success; + + g_assert (iface_modem_3gpp_parent->run_registration_checks_finish); + success = iface_modem_3gpp_parent->run_registration_checks_finish (self, res, &error); + if (!success) { + g_assert (error); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "checking if SIM is unprovisioned (ignoring registration state)"); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CEER", + 6, + FALSE, + (GAsyncReadyCallback) run_registration_checks_subscription_state_ready, + task); +} + +static void +modem_3gpp_run_registration_checks (MMIfaceModem3gpp *self, + gboolean is_cs_supported, + gboolean is_ps_supported, + gboolean is_eps_supported, + gboolean is_5gs_supported, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + g_assert (iface_modem_3gpp_parent->run_registration_checks); + iface_modem_3gpp_parent->run_registration_checks (self, + is_cs_supported, + is_ps_supported, + is_eps_supported, + is_5gs_supported, + (GAsyncReadyCallback) run_registration_checks_ready, + task); +} + +/*****************************************************************************/ +/* Register in network (3GPP interface) */ + +static gboolean +modem_3gpp_register_in_network_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cmatt_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_3gpp_register_in_network (MMIfaceModem3gpp *self, + const gchar *operator_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, cancellable, callback, user_data); + + if (operator_id) { + /* Currently only VZW is supported */ + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Setting a specific operator ID is not supported"); + g_object_unref (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "%CMATT=1", + 3, + FALSE, /* allow cached */ + (GAsyncReadyCallback)cmatt_ready, + task); +} + +/*****************************************************************************/ +/* SIMREFRESH unsolicited event handler */ + +static void +altair_reregister_ready (MMBaseModem *self, + GAsyncResult *res, + gpointer user_data) +{ + if (!mm_base_modem_at_command_finish (self, res, NULL)) + mm_obj_dbg (self, "failed to reregister modem"); + else + mm_obj_dbg (self, "modem reregistered successfully"); + MM_BROADBAND_MODEM_ALTAIR_LTE (self)->priv->sim_refresh_detach_in_progress = FALSE; +} + +static void +altair_deregister_ready (MMBaseModem *self, + GAsyncResult *res, + gpointer user_data) +{ + if (!mm_base_modem_at_command_finish (self, res, NULL)) { + mm_obj_dbg (self, "deregister modem failed"); + MM_BROADBAND_MODEM_ALTAIR_LTE (self)->priv->sim_refresh_detach_in_progress = FALSE; + return; + } + + mm_obj_dbg (self, "deregistered modem, now reregistering"); + + /* Register */ + mm_base_modem_at_command ( + self, + "%CMATT=1", + 10, + FALSE, /* allow_cached */ + (GAsyncReadyCallback)altair_reregister_ready, + NULL); +} + +static void +altair_load_own_numbers_ready (MMIfaceModem *iface_modem, + GAsyncResult *res, + MMBroadbandModemAltairLte *self) +{ + GError *error = NULL; + GStrv str_list; + + str_list = MM_IFACE_MODEM_GET_INTERFACE (self)->load_own_numbers_finish (MM_IFACE_MODEM (self), res, &error); + if (error) { + mm_obj_warn (self, "Couldn't reload Own Numbers: '%s'", error->message); + g_error_free (error); + } + if (str_list) { + mm_iface_modem_update_own_numbers (iface_modem, str_list); + g_strfreev (str_list); + } + + /* Set this flag to prevent connect requests from being processed while we + * detach from the network.*/ + self->priv->sim_refresh_detach_in_progress = TRUE; + + /* Deregister */ + mm_obj_dbg (self, "reregistering modem"); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "%CMATT=0", + 10, + FALSE, /* allow_cached */ + (GAsyncReadyCallback)altair_deregister_ready, + NULL); +} + +static gboolean +altair_sim_refresh_timer_expired (MMBroadbandModemAltairLte *self) +{ + mm_obj_dbg (self, "no more SIM refreshes, reloading own numbers and reregistering modem"); + + g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->load_own_numbers); + g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->load_own_numbers_finish); + MM_IFACE_MODEM_GET_INTERFACE (self)->load_own_numbers ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)altair_load_own_numbers_ready, + self); + self->priv->sim_refresh_timer_id = 0; + + return G_SOURCE_REMOVE; +} + +static void +altair_sim_refresh_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemAltairLte *self) +{ + mm_obj_dbg (self, "received SIM refresh notification"); + if (self->priv->sim_refresh_timer_id) { + g_source_remove (self->priv->sim_refresh_timer_id); + } + self->priv->sim_refresh_timer_id = + g_timeout_add_seconds(10, + (GSourceFunc)altair_sim_refresh_timer_expired, + self); +} + +typedef enum { + MM_STATCM_ALTAIR_LTE_DEREGISTERED = 0, + MM_STATCM_ALTAIR_LTE_REGISTERED = 1, + MM_STATCM_ALTAIR_PDN_CONNECTED = 3, + MM_STATCM_ALTAIR_PDN_DISCONNECTED = 4, +} MMStatcmAltair; + +static void +bearer_list_report_disconnect_status_foreach (MMBaseBearer *bearer, + gpointer *user_data) +{ + mm_base_bearer_report_connection_status (bearer, + MM_BEARER_CONNECTION_STATUS_DISCONNECTED); +} + +/*****************************************************************************/ +/* STATCM unsolicited event handler */ + +static void +altair_statcm_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemAltairLte *self) +{ + gint pdn_event = 0; + MMBearerList *list = NULL; + + mm_get_int_from_match_info (match_info, 1, &pdn_event); + + mm_obj_dbg (self, "PDN event detected: %d", pdn_event); + + /* Currently we only care about bearer disconnection */ + + if (pdn_event == MM_STATCM_ALTAIR_PDN_DISCONNECTED) { + /* If empty bearer list, nothing else to do */ + g_object_get (self, + MM_IFACE_MODEM_BEARER_LIST, &list, + NULL); + if (!list) + return; + + mm_bearer_list_foreach (list, + (MMBearerListForeachFunc)bearer_list_report_disconnect_status_foreach, + NULL); + + g_object_unref (list); + } + +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static void +altair_pco_info_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemAltairLte *self); + +static void +set_3gpp_unsolicited_events_handlers (MMBroadbandModemAltairLte *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable/disable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* SIM refresh handler */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->sim_refresh_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)altair_sim_refresh_changed : NULL, + enable ? self : NULL, + NULL); + + /* bearer mode related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->statcm_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)altair_statcm_changed : NULL, + enable ? self : NULL, + NULL); + + /* PCO info handler */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->pcoinfo_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)altair_pco_info_changed : NULL, + enable ? self : NULL, + NULL); + } +} + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_3gpp_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_ALTAIR_LTE (self), TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_3gpp_setup_unsolicited_events_ready, + task); +} + +static void +parent_3gpp_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Our own cleanup first */ + set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_ALTAIR_LTE (self), FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_3gpp_cleanup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Enabling unsolicited events (3GPP interface) */ + +static const MMBaseModemAtCommand unsolicited_events_enable_sequence[] = { + { "%STATCM=1", 10, FALSE, mm_base_modem_response_processor_no_result_continue }, + { "%NOTIFYEV=\"SIMREFRESH\",1", 10, FALSE, NULL }, + { "%PCOINFO=1", 10, FALSE, NULL }, + { NULL } +}; + +static gboolean +modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +own_enable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_finish (self, res, NULL, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Our own enable now */ + mm_base_modem_at_sequence ( + MM_BASE_MODEM (self), + unsolicited_events_enable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + (GAsyncReadyCallback)own_enable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's enable */ + iface_modem_3gpp_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Disabling unsolicited events (3GPP interface) */ + +static const MMBaseModemAtCommand unsolicited_events_disable_sequence[] = { + { "%STATCM=0", 10, FALSE, NULL }, + { "%NOTIFYEV=\"SIMREFRESH\",0", 10, FALSE, NULL }, + { "%PCOINFO=0", 10, FALSE, NULL }, + { NULL } +}; + +static gboolean +modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +own_disable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_finish (self, res, NULL, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Next, chain up parent's disable */ + iface_modem_3gpp_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Our own disable first */ + mm_base_modem_at_sequence ( + MM_BASE_MODEM (self), + unsolicited_events_disable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + (GAsyncReadyCallback)own_disable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Operator Code loading (3GPP interface) */ + +static gchar * +modem_3gpp_load_operator_code_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + const gchar *result; + gchar *operator_code = NULL; + + result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!result) + return NULL; + + if (!mm_3gpp_parse_cops_read_response (result, + NULL, /* mode */ + NULL, /* format */ + &operator_code, + NULL, /* act */ + self, + error)) + return NULL; + + return operator_code; +} + +static void +modem_3gpp_load_operator_code (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+COPS=3,2", + 6, + FALSE, + NULL, + NULL); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+COPS?", + 6, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Operator Name loading (3GPP interface) */ + +static gchar * +modem_3gpp_load_operator_name_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + const gchar *result; + gchar *operator_name = NULL; + + result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!result) + return NULL; + + if (!mm_3gpp_parse_cops_read_response (result, + NULL, /* mode */ + NULL, /* format */ + &operator_name, + NULL, /* act */ + self, + error)) + return NULL; + + mm_3gpp_normalize_operator (&operator_name, MM_MODEM_CHARSET_UNKNOWN, self); + return operator_name; +} + +static void +modem_3gpp_load_operator_name (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+COPS=3,0", + 6, + FALSE, + NULL, + NULL); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+COPS?", + 6, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* PCOINFO unsolicited event handler */ + +static void +altair_pco_info_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemAltairLte *self) +{ + const gchar *pco_info; + MMPco *pco; + g_autoptr(GError) error = NULL; + + pco_info = g_match_info_fetch (match_info, 0); + + /* ignore if empty */ + if (!pco_info || !pco_info[0]) + return; + + mm_obj_dbg (self, "parsing vendor PCO info: %s", pco_info); + pco = mm_altair_parse_vendor_pco_info (pco_info, &error); + if (!pco) { + mm_obj_warn (self, "error parsing vendor PCO info: %s", error->message); + return; + } + + self->priv->pco_list = mm_pco_list_add (self->priv->pco_list, pco); + mm_iface_modem_3gpp_update_pco_list (MM_IFACE_MODEM_3GPP (self), self->priv->pco_list); + g_object_unref (pco); +} + +/*****************************************************************************/ +/* Generic ports open/close context */ + +static const gchar *primary_init_sequence[] = { + /* Extended numeric codes */ + "+CMEE=1", + NULL +}; + + +static void +setup_ports (MMBroadbandModem *self) +{ + MMPortSerialAt *primary; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_altair_lte_parent_class)->setup_ports (self); + + primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + if (!primary) + return; + + g_object_set (primary, + MM_PORT_SERIAL_SEND_DELAY, (guint64) 0, + MM_PORT_SERIAL_AT_SEND_LF, TRUE, + MM_PORT_SERIAL_AT_INIT_SEQUENCE, primary_init_sequence, + NULL); +} + +/*****************************************************************************/ + +MMBroadbandModemAltairLte * +mm_broadband_modem_altair_lte_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + + return g_object_new (MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Altair bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + /* Since this is an LTE-only modem - don't bother query + * anything else */ + MM_IFACE_MODEM_3GPP_CS_NETWORK_SUPPORTED, FALSE, + MM_IFACE_MODEM_3GPP_PS_NETWORK_SUPPORTED, FALSE, + MM_IFACE_MODEM_3GPP_EPS_NETWORK_SUPPORTED, TRUE, + NULL); +} + +gboolean +mm_broadband_modem_altair_lte_is_sim_refresh_detach_in_progress (MMBroadbandModem *self) +{ + return MM_BROADBAND_MODEM_ALTAIR_LTE (self)->priv->sim_refresh_detach_in_progress; +} + +static void +mm_broadband_modem_altair_lte_init (MMBroadbandModemAltairLte *self) +{ + + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE, + MMBroadbandModemAltairLtePrivate); + + self->priv->sim_refresh_regex = g_regex_new ("\\r\\n\\%NOTIFYEV:\\s*\"?SIMREFRESH\"?,?(\\d*)\\r+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->sim_refresh_detach_in_progress = FALSE; + self->priv->sim_refresh_timer_id = 0; + self->priv->statcm_regex = g_regex_new ("\\r\\n\\%STATCM:\\s*(\\d*),?(\\d*)\\r+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->pcoinfo_regex = g_regex_new ("\\r\\n\\%PCOINFO:\\s*(\\d*),([^,\\s]*),([^,\\s]*)\\r+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemAltairLte *self = MM_BROADBAND_MODEM_ALTAIR_LTE (object); + + if (self->priv->sim_refresh_timer_id) + g_source_remove (self->priv->sim_refresh_timer_id); + g_regex_unref (self->priv->sim_refresh_regex); + g_regex_unref (self->priv->statcm_regex); + g_regex_unref (self->priv->pcoinfo_regex); + G_OBJECT_CLASS (mm_broadband_modem_altair_lte_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; + iface->load_current_capabilities = load_current_capabilities; + iface->load_current_capabilities_finish = load_current_capabilities_finish; + iface->load_supported_bands = load_supported_bands; + iface->load_supported_bands_finish = load_supported_bands_finish; + iface->load_current_bands = load_current_bands; + iface->load_current_bands_finish = load_current_bands_finish; + + iface->load_access_technologies = NULL; + iface->load_access_technologies_finish = NULL; + + iface->reset = reset; + iface->reset_finish = reset_finish; + + iface->load_supported_charsets = NULL; + iface->load_supported_charsets_finish = NULL; + iface->setup_charset = NULL; + iface->setup_charset_finish = NULL; + iface->setup_flow_control = NULL; + iface->setup_flow_control_finish = NULL; +} + +static void +iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface) +{ + /* we don't have USSD support */ + iface->check_support = NULL; + iface->check_support_finish = NULL; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish; + + iface->register_in_network = modem_3gpp_register_in_network; + iface->register_in_network_finish = modem_3gpp_register_in_network_finish; + iface->run_registration_checks = modem_3gpp_run_registration_checks; + iface->run_registration_checks_finish = modem_3gpp_run_registration_checks_finish; + + /* Scanning is not currently supported */ + iface->scan_networks = NULL; + iface->scan_networks_finish = NULL; + + /* Additional actions */ + iface->load_operator_code = modem_3gpp_load_operator_code; + iface->load_operator_code_finish = modem_3gpp_load_operator_code_finish; + iface->load_operator_name = modem_3gpp_load_operator_name; + iface->load_operator_name_finish = modem_3gpp_load_operator_name_finish; +} + +static void +iface_modem_messaging_init (MMIfaceModemMessaging *iface) +{ + /* Currently no messaging is implemented - so skip checking*/ + iface->check_support = NULL; + iface->check_support_finish = NULL; +} + +static void +mm_broadband_modem_altair_lte_class_init (MMBroadbandModemAltairLteClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemAltairLtePrivate)); + + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; + + /* The Altair LTE modem reboots itself upon receiving an ATZ command. We + * need to skip the default implementation in MMBroadbandModem to prevent + * an ATZ command from being issued as part of the modem initialization + * sequence when enabling the modem. */ + broadband_modem_class->enabling_modem_init = NULL; + broadband_modem_class->enabling_modem_init_finish = NULL; +} diff --git a/src/plugins/altair/mm-broadband-modem-altair-lte.h b/src/plugins/altair/mm-broadband-modem-altair-lte.h new file mode 100644 index 00000000..fc8d362e --- /dev/null +++ b/src/plugins/altair/mm-broadband-modem-altair-lte.h @@ -0,0 +1,53 @@ +/* -*- 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) 2013 Altair Semiconductor + * + * Author: Ori Inbar <ori.inbar@altair-semi.com> + */ + +#ifndef MM_BROADBAND_MODEM_ALTAIR_LTE_H +#define MM_BROADBAND_MODEM_ALTAIR_LTE_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE (mm_broadband_modem_altair_lte_get_type ()) +#define MM_BROADBAND_MODEM_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE, MMBroadbandModemAltairLte)) +#define MM_BROADBAND_MODEM_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE, MMBroadbandModemAltairLteClass)) +#define MM_IS_BROADBAND_MODEM_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE)) +#define MM_IS_BROADBAND_MODEM_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE)) +#define MM_BROADBAND_MODEM_ALTAIR_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE, MMBroadbandModemAltairLteClass)) + +typedef struct _MMBroadbandModemAltairLte MMBroadbandModemAltairLte; +typedef struct _MMBroadbandModemAltairLteClass MMBroadbandModemAltairLteClass; +typedef struct _MMBroadbandModemAltairLtePrivate MMBroadbandModemAltairLtePrivate; + +struct _MMBroadbandModemAltairLte { + MMBroadbandModem parent; + MMBroadbandModemAltairLtePrivate *priv; +}; + +struct _MMBroadbandModemAltairLteClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_altair_lte_get_type (void); + +MMBroadbandModemAltairLte *mm_broadband_modem_altair_lte_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +gboolean mm_broadband_modem_altair_lte_is_sim_refresh_detach_in_progress (MMBroadbandModem *self); + +#endif /* MM_BROADBAND_MODEM_ALTAIR_LTE_H */ diff --git a/src/plugins/altair/mm-modem-helpers-altair-lte.c b/src/plugins/altair/mm-modem-helpers-altair-lte.c new file mode 100644 index 00000000..d2fd9af7 --- /dev/null +++ b/src/plugins/altair/mm-modem-helpers-altair-lte.c @@ -0,0 +1,259 @@ +/* -*- 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) 2013 Google Inc. + * + */ + +#include <stdlib.h> +#include <string.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-modem-helpers-altair-lte.h" + +#define MM_ALTAIR_IMS_PDN_CID 1 +#define MM_ALTAIR_INTERNET_PDN_CID 3 + +/*****************************************************************************/ +/* Bands response parser */ + +GArray * +mm_altair_parse_bands_response (const gchar *response) +{ + gchar **split; + GArray *bands; + guint i; + + /* + * Response is "<band>[,<band>...]" + */ + split = g_strsplit_set (response, ",", -1); + if (!split) + return NULL; + + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), g_strv_length (split)); + + for (i = 0; split[i]; i++) { + guint32 band_value; + MMModemBand band; + + band_value = (guint32)strtoul (split[i], NULL, 10); + band = MM_MODEM_BAND_EUTRAN_1 - 1 + band_value; + + /* Due to a firmware issue, the modem may incorrectly includes 0 in the + * bands response. We thus ignore any band value outside the range of + * E-UTRAN operating bands. */ + if (band >= MM_MODEM_BAND_EUTRAN_1 && band <= MM_MODEM_BAND_EUTRAN_44) + g_array_append_val (bands, band); + } + + g_strfreev (split); + + return bands; +} + +/*****************************************************************************/ +/* +CEER response parser */ + +gchar * +mm_altair_parse_ceer_response (const gchar *response, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + gchar *ceer_response = NULL; + + + /* First accept an empty response as the no error case. Sometimes, the only + * response to the AT+CEER query is an OK. + */ + if (g_strcmp0 ("", response) == 0) { + return g_strdup (""); + } + + /* The response we are interested in looks so: + * +CEER: EPS_AND_NON_EPS_SERVICES_NOT_ALLOWED + */ + r = g_regex_new ("\\+CEER:\\s*(\\w*)?", + G_REGEX_RAW, + 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match (r, response, 0, &match_info)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse +CEER response"); + return NULL; + } + + if (g_match_info_matches (match_info)) { + ceer_response = mm_get_string_unquoted_from_match_info (match_info, 1); + if (!ceer_response) + ceer_response = g_strdup (""); + } + + return ceer_response; +} + +/*****************************************************************************/ +/* %CGINFO="cid",1 response parser */ + +gint +mm_altair_parse_cid (const gchar *response, GError **error) +{ + g_autoptr(GRegex) regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + guint cid = -1; + + regex = g_regex_new ("\\%CGINFO:\\s*(\\d+)", G_REGEX_RAW, 0, NULL); + g_assert (regex); + if (!g_regex_match_full (regex, response, strlen (response), 0, 0, &match_info, error)) + return -1; + + if (!mm_get_uint_from_match_info (match_info, 1, &cid)) + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse %%CGINFO=\"cid\",1 response"); + + return cid; +} + +/*****************************************************************************/ +/* %PCOINFO response parser */ + +MMPco * +mm_altair_parse_vendor_pco_info (const gchar *pco_info, GError **error) +{ + g_autoptr(GRegex) regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + MMPco *pco = NULL; + gint num_matches; + + if (!pco_info || !pco_info[0]) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, + "No PCO info given"); + return NULL; + } + + /* Expected %PCOINFO response: + * + * Solicited response: %PCOINFO:<mode>,<cid>[,<pcoid>[,<payload>]] + * Unsolicited response: %PCOINFO:<cid>,<pcoid>[,<payload>] + */ + regex = g_regex_new ("\\%PCOINFO:(?:\\s*\\d+\\s*,)?(\\d+)\\s*(,([^,\\)]*),([0-9A-Fa-f]*))?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, + 0, NULL); + g_assert (regex); + + if (!g_regex_match_full (regex, pco_info, strlen (pco_info), 0, 0, &match_info, error)) + return NULL; + + num_matches = g_match_info_get_match_count (match_info); + if (num_matches != 5) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse substrings, number of matches: %d", + num_matches); + return NULL; + } + + while (g_match_info_matches (match_info)) { + guint pco_cid; + g_autofree gchar *pco_id = NULL; + g_autofree gchar *pco_payload = NULL; + g_autofree guint8 *pco_payload_bytes = NULL; + gsize pco_payload_bytes_len; + guint8 pco_prefix[6]; + GByteArray *pco_raw; + gsize pco_raw_len; + + if (!mm_get_uint_from_match_info (match_info, 1, &pco_cid)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse CID from PCO info: '%s'", pco_info); + break; + } + + /* We are only interested in IMS and Internet PDN PCO. */ + if (pco_cid != MM_ALTAIR_IMS_PDN_CID && pco_cid != MM_ALTAIR_INTERNET_PDN_CID) { + g_match_info_next (match_info, error); + continue; + } + + pco_id = mm_get_string_unquoted_from_match_info (match_info, 3); + if (!pco_id) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse PCO ID from PCO info: '%s'", pco_info); + break; + } + + if (g_strcmp0 (pco_id, "FF00")) { + g_match_info_next (match_info, error); + continue; + } + + pco_payload = mm_get_string_unquoted_from_match_info (match_info, 4); + if (!pco_payload) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse PCO payload from PCO info: '%s'", pco_info); + break; + } + + pco_payload_bytes = mm_utils_hexstr2bin (pco_payload, -1, &pco_payload_bytes_len, error); + if (!pco_payload_bytes) { + g_prefix_error (error, "Invalid PCO payload from PCO info '%s': ", pco_info); + break; + } + + /* Protocol Configuration Options (PCO) is an information element with an + * identifier (IEI) 0x27 and contains between 3 and 253 octets. See 3GPP TS + * 24.008 for more details on PCO. + * + * NOTE: The standard uses one-based indexing, but to better correlate to the + * code, zero-based indexing is used in the description hereinafter. + * + * Octet | Value + * --------+-------------------------------------------- + * 0 | PCO IEI (= 0x27) + * 1 | Length of PCO contents (= total length - 2) + * 2 | bit 7 : ext + * | bit 6 to 3 : spare (= 0b0000) + * | bit 2 to 0 : Configuration protocol + * 3 to 4 | Element 1 ID + * 5 | Length of element 1 contents + * 6 to m | Element 1 contents + * ... | + */ + pco_raw_len = sizeof (pco_prefix) + pco_payload_bytes_len; + pco_prefix[0] = 0x27; + pco_prefix[1] = pco_raw_len - 2; + pco_prefix[2] = 0x80; + /* Verizon uses element ID 0xFF00 for carrier-specific PCO content. */ + pco_prefix[3] = 0xFF; + pco_prefix[4] = 0x00; + pco_prefix[5] = pco_payload_bytes_len; + + pco_raw = g_byte_array_sized_new (pco_raw_len); + g_byte_array_append (pco_raw, pco_prefix, sizeof (pco_prefix)); + g_byte_array_append (pco_raw, pco_payload_bytes, pco_payload_bytes_len); + + pco = mm_pco_new (); + mm_pco_set_session_id (pco, pco_cid); + mm_pco_set_complete (pco, TRUE); + mm_pco_set_data (pco, pco_raw->data, pco_raw->len); + break; + } + + return pco; +} diff --git a/src/plugins/altair/mm-modem-helpers-altair-lte.h b/src/plugins/altair/mm-modem-helpers-altair-lte.h new file mode 100644 index 00000000..ff7f64b0 --- /dev/null +++ b/src/plugins/altair/mm-modem-helpers-altair-lte.h @@ -0,0 +1,38 @@ +/* -*- 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) 2013 Google Inc. + * + */ + +#ifndef MM_MODEM_HELPERS_ALTAIR_H +#define MM_MODEM_HELPERS_ALTAIR_H + +#include <glib.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +/* Bands response parser */ +GArray *mm_altair_parse_bands_response (const gchar *response); + +/* +CEER response parser */ +gchar *mm_altair_parse_ceer_response (const gchar *response, + GError **error); + +/* %CGINFO="cid",1 response parser */ +gint mm_altair_parse_cid (const gchar *response, GError **error); + +/* %PCOINFO response parser */ +MMPco *mm_altair_parse_vendor_pco_info (const gchar *pco_info, GError **error); + +#endif /* MM_MODEM_HELPERS_ALTAIR_H */ diff --git a/src/plugins/altair/mm-plugin-altair-lte.c b/src/plugins/altair/mm-plugin-altair-lte.c new file mode 100644 index 00000000..e58fe176 --- /dev/null +++ b/src/plugins/altair/mm-plugin-altair-lte.c @@ -0,0 +1,101 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2013 Altair Semiconductor + * + * Author: Ori Inbar <ori.inbar@altair-semi.com> + */ + +#include <string.h> +#include <gmodule.h> + +#include "mm-plugin-altair-lte.h" +#include "mm-private-boxed-types.h" +#include "mm-broadband-modem-altair-lte.h" +#include "mm-log.h" + +G_DEFINE_TYPE (MMPluginAltairLte, mm_plugin_altair_lte, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ +/* Custom commands for AT probing */ + +/* Increase the response timeout for probe commands since some altair modems + take longer to respond after a reset. + */ +static const MMPortProbeAtCommand custom_at_probe[] = { + { "AT", 7, mm_port_probe_response_processor_is_at }, + { "AT", 7, mm_port_probe_response_processor_is_at }, + { "AT", 7, mm_port_probe_response_processor_is_at }, + { NULL } +}; + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_altair_lte_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", NULL }; + static const mm_uint16_pair products[] = { + { 0x216f, 0x0047 }, /* Altair NPe */ + { 0, 0 } + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_ALTAIR_LTE, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_PRODUCT_IDS, products, + MM_PLUGIN_CUSTOM_AT_PROBE, custom_at_probe, + MM_PLUGIN_ALLOWED_SINGLE_AT, TRUE, + MM_PLUGIN_SEND_LF, TRUE, + NULL)); +} + +static void +mm_plugin_altair_lte_init (MMPluginAltairLte *self) +{ +} + +static void +mm_plugin_altair_lte_class_init (MMPluginAltairLteClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/altair/mm-plugin-altair-lte.h b/src/plugins/altair/mm-plugin-altair-lte.h new file mode 100644 index 00000000..385a5cc2 --- /dev/null +++ b/src/plugins/altair/mm-plugin-altair-lte.h @@ -0,0 +1,48 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2013 Altair Semiconductor + * + * Author: Ori Inbar <ori.inbar@altair-semi.com> + */ + +#ifndef MM_PLUGIN_ALTAIR_LTE_H +#define MM_PLUGIN_ALTAIR_LTE_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_ALTAIR_LTE (mm_plugin_altair_lte_get_type ()) +#define MM_PLUGIN_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_ALTAIR_LTE, MMPluginAltairLte)) +#define MM_PLUGIN_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_ALTAIR_LTE, MMPluginAltairLteClass)) +#define MM_IS_PLUGIN_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_ALTAIR_LTE)) +#define MM_IS_PLUGIN_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_ALTAIR_LTE)) +#define MM_PLUGIN_ALTAIR_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_ALTAIR_LTE, MMPluginAltairLteClass)) + +typedef struct { + MMPlugin parent; +} MMPluginAltairLte; + +typedef struct { + MMPluginClass parent; +} MMPluginAltairLteClass; + +GType mm_plugin_altair_lte_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_ALTAIR_LTE_H */ diff --git a/src/plugins/altair/tests/test-modem-helpers-altair-lte.c b/src/plugins/altair/tests/test-modem-helpers-altair-lte.c new file mode 100644 index 00000000..da9eaf32 --- /dev/null +++ b/src/plugins/altair/tests/test-modem-helpers-altair-lte.c @@ -0,0 +1,189 @@ +/* -*- 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) 2013 Google Inc. + * + */ + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <glib.h> +#include <glib-object.h> +#include <locale.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-modem-helpers-altair-lte.h" + +/*****************************************************************************/ +/* Test bands response parsing */ + +static void +test_parse_bands (void) +{ + GArray *bands; + + bands = mm_altair_parse_bands_response (""); + g_assert (bands != NULL); + g_assert_cmpuint (bands->len, ==, 0); + g_array_free (bands, TRUE); + + /* 0 and 45 are outside the range of E-UTRAN operating bands and should be ignored. */ + bands = mm_altair_parse_bands_response ("0, 0, 1, 4,13,44,45"); + g_assert (bands != NULL); + g_assert_cmpuint (bands->len, ==, 4); + g_assert_cmpuint (g_array_index (bands, MMModemBand, 0), ==, MM_MODEM_BAND_EUTRAN_1); + g_assert_cmpuint (g_array_index (bands, MMModemBand, 1), ==, MM_MODEM_BAND_EUTRAN_4); + g_assert_cmpuint (g_array_index (bands, MMModemBand, 2), ==, MM_MODEM_BAND_EUTRAN_13); + g_assert_cmpuint (g_array_index (bands, MMModemBand, 3), ==, MM_MODEM_BAND_EUTRAN_44); + g_array_free (bands, TRUE); +} + +/*****************************************************************************/ +/* Test +CEER responses */ + +typedef struct { + const gchar *str; + const gchar *result; +} CeerTest; + +static const CeerTest ceer_tests[] = { + { "", "" }, /* Special case, sometimes the response is empty, treat it as a good response. */ + { "+CEER:", "" }, + { "+CEER: EPS_AND_NON_EPS_SERVICES_NOT_ALLOWED", "EPS_AND_NON_EPS_SERVICES_NOT_ALLOWED" }, + { "+CEER: NO_SUITABLE_CELLS_IN_TRACKING_AREA", "NO_SUITABLE_CELLS_IN_TRACKING_AREA" }, + { "WRONG RESPONSE", NULL }, + { NULL, NULL } +}; + +static void +test_ceer (void) +{ + guint i; + + for (i = 0; ceer_tests[i].str; ++i) { + GError *error = NULL; + gchar *result; + + result = mm_altair_parse_ceer_response (ceer_tests[i].str, &error); + if (ceer_tests[i].result) { + g_assert_cmpstr (ceer_tests[i].result, ==, result); + g_assert_no_error (error); + g_free (result); + } + else { + g_assert (result == NULL); + g_assert (error != NULL); + g_error_free (error); + } + } +} + +static void +test_parse_cid (void) +{ + g_assert (mm_altair_parse_cid ("%CGINFO: 2", NULL) == 2); + g_assert (mm_altair_parse_cid ("%CGINFO:blah", NULL) == -1); +} + +/*****************************************************************************/ +/* Test %PCOINFO responses */ + +typedef struct { + const gchar *pco_info; + guint32 session_id; + gsize pco_data_size; + guint8 pco_data[50]; +} TestValidPcoInfo; + +static const TestValidPcoInfo good_pco_infos[] = { + /* Valid PCO values */ + { "%PCOINFO: 1,1,FF00,13018400", 1, 10, + { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x00 } }, + { "%PCOINFO: 1,1,FF00,13018403", 1, 10, + { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x03 } }, + { "%PCOINFO: 1,1,FF00,13018405", 1, 10, + { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x05 } }, + { "%PCOINFO: 1,3,FF00,13018400", 3, 10, + { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x00 } }, + { "%PCOINFO: 1,3,FF00,13018403", 3, 10, + { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x03 } }, + { "%PCOINFO: 1,3,FF00,13018405", 3, 10, + { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x05 } }, + { "%PCOINFO:1,FF00,13018400", 1, 10, + { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x00 } }, + { "%PCOINFO:1,FF00,13018403", 1, 10, + { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x03 } }, + { "%PCOINFO:1,FF00,13018405", 1, 10, + { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x05 } }, + /* Different payload */ + { "%PCOINFO: 1,3,FF00,130185", 3, 9, + { 0x27, 0x07, 0x80, 0xFF, 0x00, 0x03, 0x13, 0x01, 0x85 } }, + /* Multiline PCO info */ + { "%PCOINFO: 1,2,FF00,13018400\r\n%PCOINFO: 1,3,FF00,13018403", 3, 10, + { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x03 } }, +}; + +static const gchar *bad_pco_infos[] = { + /* Different container */ + "%PCOINFO: 1,3,F000,13018401", + /* Ingood CID */ + "%PCOINFO: 1,2,FF00,13018401", + /* Bad PCO info */ + "%PCOINFO: blah,blah,FF00,13018401", + /* Bad PCO payload */ + "%PCOINFO: 1,1,FF00,130184011", +}; + +static void +test_parse_vendor_pco_info (void) +{ + MMPco *pco; + guint i; + + for (i = 0; i < G_N_ELEMENTS (good_pco_infos); ++i) { + const guint8 *pco_data; + gsize pco_data_size; + + pco = mm_altair_parse_vendor_pco_info (good_pco_infos[i].pco_info, NULL); + g_assert (pco != NULL); + g_assert_cmpuint (mm_pco_get_session_id (pco), ==, good_pco_infos[i].session_id); + g_assert (mm_pco_is_complete (pco)); + pco_data = mm_pco_get_data (pco, &pco_data_size); + g_assert (pco_data != NULL); + g_assert_cmpuint (pco_data_size, ==, good_pco_infos[i].pco_data_size); + g_assert_cmpint (memcmp (pco_data, good_pco_infos[i].pco_data, pco_data_size), ==, 0); + g_object_unref (pco); + } + + for (i = 0; i < G_N_ELEMENTS (bad_pco_infos); ++i) { + pco = mm_altair_parse_vendor_pco_info (bad_pco_infos[i], NULL); + g_assert (pco == NULL); + } +} + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/altair/parse_bands", test_parse_bands); + g_test_add_func ("/MM/altair/ceer", test_ceer); + g_test_add_func ("/MM/altair/parse_cid", test_parse_cid); + g_test_add_func ("/MM/altair/parse_vendor_pco_info", test_parse_vendor_pco_info); + + return g_test_run (); +} diff --git a/src/plugins/anydata/mm-broadband-modem-anydata.c b/src/plugins/anydata/mm-broadband-modem-anydata.c new file mode 100644 index 00000000..36d72e56 --- /dev/null +++ b/src/plugins/anydata/mm-broadband-modem-anydata.c @@ -0,0 +1,355 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-serial-parsers.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-errors-types.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-anydata.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-cdma.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_cdma_init (MMIfaceModemCdma *iface); + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemAnydata, mm_broadband_modem_anydata, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init)) + +/*****************************************************************************/ +/* Detailed registration state (CDMA interface) */ +typedef struct { + MMModemCdmaRegistrationState detailed_cdma1x_state; + MMModemCdmaRegistrationState detailed_evdo_state; +} DetailedRegistrationStateResults; + +static gboolean +get_detailed_registration_state_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + MMModemCdmaRegistrationState *detailed_cdma1x_state, + MMModemCdmaRegistrationState *detailed_evdo_state, + GError **error) +{ + DetailedRegistrationStateResults *results; + + results = g_task_propagate_pointer (G_TASK (res), error); + if (!results) + return FALSE; + + *detailed_cdma1x_state = results->detailed_cdma1x_state; + *detailed_evdo_state = results->detailed_evdo_state; + g_free (results); + return TRUE; +} + +static void +hstate_ready (MMIfaceModemCdma *self, + GAsyncResult *res, + GTask *task) +{ + DetailedRegistrationStateResults *results; + GError *error = NULL; + const gchar *response; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + results = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) { + /* Leave superclass' reg state alone if AT*HSTATE isn't supported */ + g_error_free (error); + + g_task_return_pointer (task, g_memdup (results, sizeof (*results)), g_free); + g_object_unref (task); + return; + } + + response = mm_strip_tag (response, "*HSTATE:"); + + /* Format is "<at state>,<session state>,<channel>,<pn>,<EcIo>,<rssi>,..." */ + r = g_regex_new ("\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*([^,\\)]*)\\s*,\\s*([^,\\)]*)\\s*,.*", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (r != NULL); + + g_regex_match (r, response, 0, &match_info); + if (g_match_info_get_match_count (match_info) >= 6) { + guint val = 0; + gint dbm = 0; + + /* dBm is between -106 (worst) and -20.7 (best) */ + mm_get_int_from_match_info (match_info, 6, &dbm); + + /* Parse the EVDO radio state */ + if (mm_get_uint_from_match_info (match_info, 1, &val)) { + switch (val) { + case 3: /* IDLE */ + /* If IDLE and the EVDO dBm is -105 or lower, assume no service. + * It may be that IDLE actually means NO SERVICE too; not sure. + */ + if (dbm > -105) + results->detailed_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + break; + case 4: /* ACCESS */ + case 5: /* CONNECT */ + results->detailed_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + break; + default: + mm_obj_warn (self, "unknown *HSTATE (%d); assuming no service", val); + /* fall through */ + case 0: /* NO SERVICE */ + case 1: /* ACQUISITION */ + case 2: /* SYNC */ + break; + } + } + } + + g_task_return_pointer (task, g_memdup (results, sizeof (*results)), g_free); + g_object_unref (task); +} + +static void +state_ready (MMIfaceModemCdma *self, + GAsyncResult *res, + GTask *task) +{ + DetailedRegistrationStateResults *results; + GError *error = NULL; + const gchar *response; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + results = g_task_get_task_data (task); + response = mm_strip_tag (response, "*STATE:"); + + /* Format is "<channel>,<pn>,<sid>,<nid>,<state>,<rssi>,..." */ + r = g_regex_new ("\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*([^,\\)]*)\\s*,.*", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (r != NULL); + + g_regex_match (r, response, 0, &match_info); + if (g_match_info_get_match_count (match_info) >= 6) { + guint val = 0; + gint dbm = 0; + + /* dBm is between -106 (worst) and -20.7 (best) */ + mm_get_int_from_match_info (match_info, 6, &dbm); + + /* Parse the 1x radio state */ + if (mm_get_uint_from_match_info (match_info, 5, &val)) { + switch (val) { + case 1: /* IDLE */ + /* If IDLE and the 1X dBm is -105 or lower, assume no service. + * It may be that IDLE actually means NO SERVICE too; not sure. + */ + if (dbm > -105) + results->detailed_cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + break; + case 2: /* ACCESS */ + case 3: /* PAGING */ + case 4: /* TRAFFIC */ + results->detailed_cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + break; + default: + mm_obj_warn (self, "unknown *HSTATE (%d); assuming no service", val); + /* fall through */ + case 0: /* NO SERVICE */ + break; + } + } + } + + /* Try for EVDO state too */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*HSTATE?", + 3, + FALSE, + (GAsyncReadyCallback)hstate_ready, + task); +} + +static void +get_detailed_registration_state (MMIfaceModemCdma *self, + MMModemCdmaRegistrationState cdma1x_state, + MMModemCdmaRegistrationState evdo_state, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DetailedRegistrationStateResults *results; + GTask *task; + + results = g_new (DetailedRegistrationStateResults, 1); + results->detailed_cdma1x_state = cdma1x_state; + results->detailed_evdo_state = evdo_state; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, results, g_free); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*STATE?", + 3, + FALSE, + (GAsyncReadyCallback)state_ready, + task); +} + +/*****************************************************************************/ +/* Reset (Modem interface) */ + +static gboolean +reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*RESET", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *self) +{ + MMPortSerialAt *ports[2]; + g_autoptr(GRegex) active_regex = NULL; + g_autoptr(GRegex) inactive_regex = NULL; + g_autoptr(GRegex) dormant_regex = NULL; + g_autoptr(GRegex) offline_regex = NULL; + g_autoptr(GRegex) regreq_regex = NULL; + g_autoptr(GRegex) authreq_regex = NULL; + guint i; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_anydata_parent_class)->setup_ports (self); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Data call has connected */ + active_regex = g_regex_new ("\\r\\n\\*ACTIVE:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + /* Data call disconnected */ + inactive_regex = g_regex_new ("\\r\\n\\*INACTIVE:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + /* Modem is now dormant */ + dormant_regex = g_regex_new ("\\r\\n\\*DORMANT:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + /* Network acquisition fail */ + offline_regex = g_regex_new ("\\r\\n\\*OFFLINE:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + /* Registration fail */ + regreq_regex = g_regex_new ("\\r\\n\\*REGREQ:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + /* Authentication fail */ + authreq_regex = g_regex_new ("\\r\\n\\*AUTHREQ:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + /* Now reset the unsolicited messages */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* Data state notifications */ + mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), active_regex, NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), inactive_regex, NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), dormant_regex, NULL, NULL, NULL); + + /* Abnormal state notifications + * + * FIXME: set 1X/EVDO registration state to UNKNOWN when these + * notifications are received? + */ + mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), offline_regex, NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), regreq_regex, NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), authreq_regex, NULL, NULL, NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemAnydata * +mm_broadband_modem_anydata_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_ANYDATA, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_anydata_init (MMBroadbandModemAnydata *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface->reset = reset; + iface->reset_finish = reset_finish; +} + +static void +iface_modem_cdma_init (MMIfaceModemCdma *iface) +{ + iface->get_cdma1x_serving_system = NULL; + iface->get_cdma1x_serving_system_finish = NULL; + iface->get_detailed_registration_state = get_detailed_registration_state; + iface->get_detailed_registration_state_finish = get_detailed_registration_state_finish; +} + +static void +mm_broadband_modem_anydata_class_init (MMBroadbandModemAnydataClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/anydata/mm-broadband-modem-anydata.h b/src/plugins/anydata/mm-broadband-modem-anydata.h new file mode 100644 index 00000000..94145623 --- /dev/null +++ b/src/plugins/anydata/mm-broadband-modem-anydata.h @@ -0,0 +1,49 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 - Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_ANYDATA_H +#define MM_BROADBAND_MODEM_ANYDATA_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_ANYDATA (mm_broadband_modem_anydata_get_type ()) +#define MM_BROADBAND_MODEM_ANYDATA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_ANYDATA, MMBroadbandModemAnydata)) +#define MM_BROADBAND_MODEM_ANYDATA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_ANYDATA, MMBroadbandModemAnydataClass)) +#define MM_IS_BROADBAND_MODEM_ANYDATA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_ANYDATA)) +#define MM_IS_BROADBAND_MODEM_ANYDATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_ANYDATA)) +#define MM_BROADBAND_MODEM_ANYDATA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_ANYDATA, MMBroadbandModemAnydataClass)) + +typedef struct _MMBroadbandModemAnydata MMBroadbandModemAnydata; +typedef struct _MMBroadbandModemAnydataClass MMBroadbandModemAnydataClass; + +struct _MMBroadbandModemAnydata { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemAnydataClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_anydata_get_type (void); + +MMBroadbandModemAnydata *mm_broadband_modem_anydata_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_ANYDATA_H */ diff --git a/src/plugins/anydata/mm-plugin-anydata.c b/src/plugins/anydata/mm-plugin-anydata.c new file mode 100644 index 00000000..4b8f0a84 --- /dev/null +++ b/src/plugins/anydata/mm-plugin-anydata.c @@ -0,0 +1,97 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-anydata.h" +#include "mm-broadband-modem-anydata.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi.h" +#endif + +G_DEFINE_TYPE (MMPluginAnydata, mm_plugin_anydata, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered AnyDATA modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_anydata_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x16d5, 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_ANYDATA, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_REQUIRED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + NULL)); +} + +static void +mm_plugin_anydata_init (MMPluginAnydata *self) +{ +} + +static void +mm_plugin_anydata_class_init (MMPluginAnydataClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/anydata/mm-plugin-anydata.h b/src/plugins/anydata/mm-plugin-anydata.h new file mode 100644 index 00000000..765c19a5 --- /dev/null +++ b/src/plugins/anydata/mm-plugin-anydata.h @@ -0,0 +1,43 @@ + +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_ANYDATA_H +#define MM_PLUGIN_ANYDATA_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_ANYDATA (mm_plugin_anydata_get_type ()) +#define MM_PLUGIN_ANYDATA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_ANYDATA, MMPluginAnydata)) +#define MM_PLUGIN_ANYDATA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_ANYDATA, MMPluginAnydataClass)) +#define MM_IS_PLUGIN_ANYDATA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_ANYDATA)) +#define MM_IS_PLUGIN_ANYDATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_ANYDATA)) +#define MM_PLUGIN_ANYDATA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_ANYDATA, MMPluginAnydataClass)) + +typedef struct { + MMPlugin parent; +} MMPluginAnydata; + +typedef struct { + MMPluginClass parent; +} MMPluginAnydataClass; + +GType mm_plugin_anydata_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_ANYDATA_H */ diff --git a/src/plugins/broadmobi/77-mm-broadmobi-port-types.rules b/src/plugins/broadmobi/77-mm-broadmobi-port-types.rules new file mode 100644 index 00000000..685e1467 --- /dev/null +++ b/src/plugins/broadmobi/77-mm-broadmobi-port-types.rules @@ -0,0 +1,16 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_broadmobi_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2020", GOTO="mm_broadmobi_port_types" +GOTO="mm_broadmobi_port_types_end" + +LABEL="mm_broadmobi_port_types" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# BroadMobi BM818 +ATTRS{idVendor}=="2020", ATTRS{idProduct}=="2060", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2020", ATTRS{idProduct}=="2060", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2020", ATTRS{idProduct}=="2060", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="2020", ATTRS{idProduct}=="2060", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +LABEL="mm_broadmobi_port_types_end"
\ No newline at end of file diff --git a/src/plugins/broadmobi/mm-plugin-broadmobi.c b/src/plugins/broadmobi/mm-plugin-broadmobi.c new file mode 100644 index 00000000..805663fe --- /dev/null +++ b/src/plugins/broadmobi/mm-plugin-broadmobi.c @@ -0,0 +1,95 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-broadmobi.h" +#include "mm-broadband-modem.h" + +#if defined WITH_QMI +# include "mm-broadband-modem-qmi.h" +#endif + +G_DEFINE_TYPE (MMPluginBroadmobi, mm_plugin_broadmobi, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered BroadMobi modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x2020, 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_BROADMOBI, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_REQUIRED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + NULL)); +} + +static void +mm_plugin_broadmobi_init (MMPluginBroadmobi *self) +{ +} + +static void +mm_plugin_broadmobi_class_init (MMPluginBroadmobiClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/broadmobi/mm-plugin-broadmobi.h b/src/plugins/broadmobi/mm-plugin-broadmobi.h new file mode 100644 index 00000000..1f46cfce --- /dev/null +++ b/src/plugins/broadmobi/mm-plugin-broadmobi.h @@ -0,0 +1,40 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_BROADMOBI_H +#define MM_PLUGIN_BROADMOBI_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_BROADMOBI (mm_plugin_broadmobi_get_type ()) +#define MM_PLUGIN_BROADMOBI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_BROADMOBI, MMPluginBroadmobi)) +#define MM_PLUGIN_BROADMOBI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_BROADMOBI, MMPluginBroadmobiClass)) +#define MM_IS_PLUGIN_BROADMOBI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_BROADMOBI)) +#define MM_IS_PLUGIN_BROADMOBI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_BROADMOBI)) +#define MM_PLUGIN_BROADMOBI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_BROADMOBI, MMPluginBroadmobiClass)) + +typedef struct { + MMPlugin parent; +} MMPluginBroadmobi; + +typedef struct { + MMPluginClass parent; +} MMPluginBroadmobiClass; + +GType mm_plugin_broadmobi_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_BROADMOBI_H */ diff --git a/src/plugins/cinterion/77-mm-cinterion-port-types.rules b/src/plugins/cinterion/77-mm-cinterion-port-types.rules new file mode 100644 index 00000000..c1a9bc4a --- /dev/null +++ b/src/plugins/cinterion/77-mm-cinterion-port-types.rules @@ -0,0 +1,71 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_cinterion_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1e2d", GOTO="mm_cinterion_port_types" +GOTO="mm_cinterion_port_types_end" + +LABEL="mm_cinterion_port_types" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# PHS8 +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0053", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" + +# PLS8 port types +# ttyACM0 (if #0): AT port +# ttyACM1 (if #2): AT port +# ttyACM2 (if #4): GPS data port +# ttyACM3 (if #6): unknown +# ttyACM4 (if #8): unknown +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1" + +# PLS62 family non-mbim enumeration uses alternate settings for 2G band management +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{ID_MM_CINTERION_MODEM_FAMILY}="imt" +# PLS62 family non-mbim enumeration +# ttyACM0 (if #0): AT port +# ttyACM1 (if #2): AT port +# ttyACM2 (if #4): can be AT or GNSS in some models +# ttyACM3 (if #6): AT port (but just ignore) +# ttyACM4 (if #8): DIAG/QCDM +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="08", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" + +# PLS62 family mbim enumeration +# ttyACM0 (if #0): AT port +# ttyACM1 (if #2): AT port +# ttyACM2 (if #4): can be AT or GNSS in some models +# ttyACM3 (if #6): AT port (but just ignore) +# ttyACM4 (if #8): DIAG/QCDM +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="08", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" + +# PLS63 +# ttyACM0 (if #0): AT port +# ttyACM1 (if #2): AT port +# ttyACM2 (if #4): GPS data port +# ttyACM3 (if #6): DIAG/QCDM +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" + +# PLS83 +# ttyACM0 (if #0): AT port +# ttyACM1 (if #2): AT port +# ttyACM2 (if #4): GPS data port +# ttyACM3 (if #6): DIAG/QCDM +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" + +LABEL="mm_cinterion_port_types_end" diff --git a/src/plugins/cinterion/mm-broadband-bearer-cinterion.c b/src/plugins/cinterion/mm-broadband-bearer-cinterion.c new file mode 100644 index 00000000..85fbf69c --- /dev/null +++ b/src/plugins/cinterion/mm-broadband-bearer-cinterion.c @@ -0,0 +1,796 @@ +/* -*- 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) 2016 Trimble Navigation Limited + * Author: Matthew Stanger <matthew_stanger@trimble.com> + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <arpa/inet.h> +#include <ModemManager.h> +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-cinterion.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-cinterion.h" +#include "mm-daemon-enums-types.h" + +G_DEFINE_TYPE (MMBroadbandBearerCinterion, mm_broadband_bearer_cinterion, MM_TYPE_BROADBAND_BEARER) + +/*****************************************************************************/ +/* WWAN interface mapping */ + +typedef struct { + guint swwan_index; + guint usb_iface_num; +} UsbInterfaceConfig; + +/* Map SWWAN index, USB interface number and preferred PDP context. + * + * The expected USB interface mapping is: + * INTERFACE=usb0 -> ID_USB_INTERFACE_NUM=0a + * INTERFACE=usb1 -> ID_USB_INTERFACE_NUM=0c + * INTERFACE=usb0 -> ID_USB_INTERFACE_NUM=08 (PLSx3w) + */ +static const UsbInterfaceConfig usb_interface_configs[] = { + { + .swwan_index = 1, + .usb_iface_num = 0x0a, + }, + { + .swwan_index = 2, + .usb_iface_num = 0x0c, + }, + { + .swwan_index = 1, + .usb_iface_num = 0x08, + }, +}; + +static gint +get_usb_interface_config_index (MMPort *data, + GError **error) +{ + guint usb_iface_num; + guint i; + + usb_iface_num = (guint) mm_kernel_device_get_interface_number (mm_port_peek_kernel_device (data)); + + for (i = 0; i < G_N_ELEMENTS (usb_interface_configs); i++) { + if (usb_interface_configs[i].usb_iface_num == usb_iface_num) + return (gint) i; + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unsupported WWAN interface: unexpected interface number: 0x%02x", usb_iface_num); + return -1; +} + +/*****************************************************************************/ +/* Connection status loading + * NOTE: only CONNECTED or DISCONNECTED should be reported here. + */ + +static MMBearerConnectionStatus +load_connection_status_finish (MMBaseBearer *bearer, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize aux; + + aux = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_BEARER_CONNECTION_STATUS_UNKNOWN; + } + return (MMBearerConnectionStatus) aux; +} + +typedef struct { + guint cid; + guint retries; + gboolean delay; + gboolean retry; +} LoadConnectionContext; + +static void +load_connection_context_free (LoadConnectionContext *ctx) +{ + g_slice_free (LoadConnectionContext, ctx); +} + +static gboolean swwan_check_status (GTask *task); + +static void +swwan_check_status_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerCinterion *self; + const gchar *response; + GError *error = NULL; + MMBearerConnectionStatus status; + LoadConnectionContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response) { + g_task_return_error (task, error); + goto out; + } + + status = mm_cinterion_parse_swwan_response (response, ctx->cid, self, &error); + if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) { + g_task_return_error (task, error); + goto out; + } else if (ctx->retry && status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) { + mm_obj_dbg (self, "check status retry"); + if (ctx->retries == 0) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "CID %u status check retry exceeded", ctx->cid); + goto out; + } else { + if (ctx->delay) { + g_timeout_add_seconds (1, (GSourceFunc)swwan_check_status, task); + } else { + g_idle_add ((GSourceFunc)swwan_check_status, task); + } + ctx->retries--; + return; + } + } + + g_assert (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED || + status == MM_BEARER_CONNECTION_STATUS_CONNECTED); + g_task_return_int (task, (gssize) status); + +out: + g_object_unref (task); +} + +static gboolean +swwan_check_status (GTask *task) +{ + MMBroadbandBearerCinterion *bearer; + g_autoptr(MMBaseModem) modem = NULL; + + bearer = g_task_get_source_object (task); + g_object_get (bearer, + MM_BASE_BEARER_MODEM, &modem, + NULL); + mm_base_modem_at_command (modem, + "^SWWAN?", + 5, + FALSE, + (GAsyncReadyCallback) swwan_check_status_ready, + task); + + return G_SOURCE_REMOVE; +} + +static void +load_connection_status_by_cid (MMBroadbandBearerCinterion *bearer, + gint cid, + gboolean delay, + gboolean retry, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LoadConnectionContext *ctx; + + task = g_task_new (bearer, NULL, callback, user_data); + if (cid == MM_3GPP_PROFILE_ID_UNKNOWN) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown profile id to check connection status"); + g_object_unref (task); + return; + } + + ctx = g_slice_new0 (LoadConnectionContext); + g_task_set_task_data (task, ctx, (GDestroyNotify) load_connection_context_free); + + /* Setup context */ + ctx->cid = cid; + ctx->retries = 5; + ctx->delay = delay; + ctx->retry = retry; + + /* Some modems require a delay before querying the SWWAN status + * This is only needed for step DIAL_3GPP_CONTEXT_STEP_VALIDATE_CONNECTION + * and DISCONNECT_3GPP_CONTEXT_STEP_CONNECTION_STATUS. */ + if (delay) { + g_timeout_add_seconds (1, (GSourceFunc)swwan_check_status, task); + } else { + g_idle_add ((GSourceFunc)swwan_check_status, task); + } +} + +static void +load_connection_status (MMBaseBearer *bearer, + GAsyncReadyCallback callback, + gpointer user_data) +{ + load_connection_status_by_cid (MM_BROADBAND_BEARER_CINTERION (bearer), + mm_base_bearer_get_profile_id (bearer), + FALSE, + FALSE, + callback, + user_data); +} + +/******************************************************************************/ +/* Dial 3GPP */ + +typedef enum { + DIAL_3GPP_CONTEXT_STEP_FIRST = 0, + DIAL_3GPP_CONTEXT_STEP_AUTH, + DIAL_3GPP_CONTEXT_STEP_START_SWWAN, + DIAL_3GPP_CONTEXT_STEP_VALIDATE_CONNECTION, + DIAL_3GPP_CONTEXT_STEP_LAST, +} Dial3gppContextStep; + +typedef struct { + MMBroadbandBearerCinterion *self; + MMBaseModem *modem; + MMPortSerialAt *primary; + guint cid; + MMPort *data; + gint usb_interface_config_index; + Dial3gppContextStep step; +} Dial3gppContext; + +static void +dial_3gpp_context_free (Dial3gppContext *ctx) +{ + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_object_unref (ctx->primary); + g_clear_object (&ctx->data); + g_slice_free (Dial3gppContext, ctx); +} + +static MMPort * +dial_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return MM_PORT (g_task_propagate_pointer (G_TASK (res), error)); +} + +static void dial_3gpp_context_step (GTask *task); + +static void +dial_connection_status_ready (MMBroadbandBearerCinterion *self, + GAsyncResult *res, + GTask *task) +{ + MMBearerConnectionStatus status; + Dial3gppContext *ctx; + GError *error = NULL; + + ctx = (Dial3gppContext *) g_task_get_task_data (task); + + status = load_connection_status_finish (MM_BASE_BEARER (self), res, &error); + if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "CID %u is reported disconnected", ctx->cid); + g_object_unref (task); + return; + } + + g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED); + + /* Go to next step */ + ctx->step++; + dial_3gpp_context_step (task); +} + +static void +common_dial_operation_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + Dial3gppContext *ctx; + GError *error = NULL; + + ctx = (Dial3gppContext *) g_task_get_task_data (task); + + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go to next step */ + ctx->step++; + dial_3gpp_context_step (task); +} + +static void +swwan_dial_operation_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerCinterion *self) /* full ref! */ +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + mm_obj_warn (self, "data connection attempt failed: %s", error->message); + mm_base_bearer_report_connection_status (MM_BASE_BEARER (self), + MM_BEARER_CONNECTION_STATUS_DISCONNECTED); + g_error_free (error); + } + + g_object_unref (self); +} + +static void +handle_cancel_dial (GTask *task) +{ + Dial3gppContext *ctx; + gchar *command; + + ctx = (Dial3gppContext *) g_task_get_task_data (task); + + /* Disconnect, may not succeed. Will not check response on cancel */ + command = g_strdup_printf ("^SWWAN=0,%u,%u", + ctx->cid, usb_interface_configs[ctx->usb_interface_config_index].swwan_index); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, + NULL, + NULL, + NULL); + g_free (command); +} + +static void +dial_3gpp_context_step (GTask *task) +{ + MMBroadbandBearerCinterion *self; + Dial3gppContext *ctx; + MMCinterionModemFamily modem_family; + gboolean default_swwan_behavior; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* Check for cancellation */ + if (g_task_return_error_if_cancelled (task)) { + handle_cancel_dial (task); + g_object_unref (task); + return; + } + + modem_family = mm_broadband_modem_cinterion_get_family (MM_BROADBAND_MODEM_CINTERION (ctx->modem)); + default_swwan_behavior = modem_family == MM_CINTERION_MODEM_FAMILY_DEFAULT; + + switch (ctx->step) { + case DIAL_3GPP_CONTEXT_STEP_FIRST: + ctx->step++; + /* fall through */ + + case DIAL_3GPP_CONTEXT_STEP_AUTH: { + g_autofree gchar *command = NULL; + + command = mm_cinterion_build_auth_string (self, + modem_family, + mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)), + ctx->cid); + + if (command) { + mm_obj_dbg (self, "dial step %u/%u: authenticating...", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST); + /* Send SGAUTH write, if User & Pass are provided. + * advance to next state by callback */ + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 10, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback) common_dial_operation_ready, + task); + return; + } + + mm_obj_dbg (self, "dial step %u/%u: authentication not required", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST); + ctx->step++; + } /* fall through */ + + case DIAL_3GPP_CONTEXT_STEP_START_SWWAN: { + g_autofree gchar *command = NULL; + + mm_obj_dbg (self, "dial step %u/%u: starting SWWAN interface %u connection...", + ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST, usb_interface_configs[ctx->usb_interface_config_index].swwan_index); + command = g_strdup_printf ("^SWWAN=1,%u,%u", + ctx->cid, + usb_interface_configs[ctx->usb_interface_config_index].swwan_index); + + if (default_swwan_behavior) { + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback) common_dial_operation_ready, + task); + return; + } + + /* We "jump" to the last step here here since the modem expects the + * DHCP discover packet while ^SWWAN runs. If the command fails, + * we'll mark the bearer disconnected later in the callback. + */ + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback) swwan_dial_operation_ready, + g_object_ref (self)); + ctx->step = DIAL_3GPP_CONTEXT_STEP_LAST; + dial_3gpp_context_step (task); + return; + } + + case DIAL_3GPP_CONTEXT_STEP_VALIDATE_CONNECTION: + g_assert (default_swwan_behavior); + mm_obj_dbg (self, "dial step %u/%u: checking SWWAN interface %u status...", + ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST, usb_interface_configs[ctx->usb_interface_config_index].swwan_index); + load_connection_status_by_cid (ctx->self, + (gint) ctx->cid, + TRUE, + TRUE, + (GAsyncReadyCallback) dial_connection_status_ready, + task); + return; + + case DIAL_3GPP_CONTEXT_STEP_LAST: + mm_obj_dbg (self, "dial step %u/%u: finished", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST); + g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +dial_3gpp (MMBroadbandBearer *self, + MMBaseModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Dial3gppContext *ctx; + GError *error = NULL; + + g_assert (primary != NULL); + + /* Setup task and create connection context */ + task = g_task_new (self, cancellable, callback, user_data); + ctx = g_slice_new0 (Dial3gppContext); + g_task_set_task_data (task, ctx, (GDestroyNotify) dial_3gpp_context_free); + + /* Setup context */ + ctx->self = MM_BROADBAND_BEARER_CINTERION (g_object_ref (self)); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + ctx->step = DIAL_3GPP_CONTEXT_STEP_FIRST; + + /* Get a net port to setup the connection on */ + ctx->data = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET); + if (!ctx->data) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to launch connection"); + g_object_unref (task); + return; + } + g_object_ref (ctx->data); + + /* Validate configuration */ + ctx->usb_interface_config_index = get_usb_interface_config_index (ctx->data, &error); + if (ctx->usb_interface_config_index < 0) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Run! */ + dial_3gpp_context_step (task); +} + +/*****************************************************************************/ +/* Disconnect 3GPP */ + +typedef enum { + DISCONNECT_3GPP_CONTEXT_STEP_FIRST, + DISCONNECT_3GPP_CONTEXT_STEP_STOP_SWWAN, + DISCONNECT_3GPP_CONTEXT_STEP_CONNECTION_STATUS, + DISCONNECT_3GPP_CONTEXT_STEP_LAST, +} Disconnect3gppContextStep; + +typedef struct { + MMBroadbandBearerCinterion *self; + MMBaseModem *modem; + MMPortSerialAt *primary; + MMPort *data; + guint cid; + gint usb_interface_config_index; + Disconnect3gppContextStep step; +} Disconnect3gppContext; + +static void +disconnect_3gpp_context_free (Disconnect3gppContext *ctx) +{ + g_object_unref (ctx->data); + g_object_unref (ctx->primary); + g_object_unref (ctx->self); + g_object_unref (ctx->modem); + g_slice_free (Disconnect3gppContext, ctx); +} + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void disconnect_3gpp_context_step (GTask *task); + +static void +disconnect_connection_status_ready (MMBroadbandBearerCinterion *self, + GAsyncResult *res, + GTask *task) +{ + MMBearerConnectionStatus status; + Disconnect3gppContext *ctx; + GError *error = NULL; + + ctx = (Disconnect3gppContext *) g_task_get_task_data (task); + + status = load_connection_status_finish (MM_BASE_BEARER (self), res, &error); + switch (status) { + case MM_BEARER_CONNECTION_STATUS_UNKNOWN: + /* Assume disconnected */ + mm_obj_dbg (self, "couldn't get CID %u status, assume disconnected: %s", ctx->cid, error->message); + g_clear_error (&error); + break; + case MM_BEARER_CONNECTION_STATUS_DISCONNECTED: + break; + case MM_BEARER_CONNECTION_STATUS_CONNECTED: + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "CID %u is reported connected", ctx->cid); + g_object_unref (task); + return; + case MM_BEARER_CONNECTION_STATUS_DISCONNECTING: + case MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED: + default: + g_assert_not_reached (); + } + + /* Go on to next step */ + ctx->step++; + disconnect_3gpp_context_step (task); +} + +static void +swwan_disconnect_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + Disconnect3gppContext *ctx; + + ctx = (Disconnect3gppContext *) g_task_get_task_data (task); + + /* We don't bother to check error or response here since, ctx flow's + * next step checks it */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + + /* Go on to next step */ + ctx->step++; + disconnect_3gpp_context_step (task); +} + +static void +disconnect_3gpp_context_step (GTask *task) +{ + MMBroadbandBearerCinterion *self; + Disconnect3gppContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case DISCONNECT_3GPP_CONTEXT_STEP_FIRST: + ctx->step++; + /* fall through */ + + case DISCONNECT_3GPP_CONTEXT_STEP_STOP_SWWAN: { + gchar *command; + + command = g_strdup_printf ("^SWWAN=0,%u,%u", + ctx->cid, usb_interface_configs[ctx->usb_interface_config_index].swwan_index); + mm_obj_dbg (self, "disconnect step %u/%u: disconnecting PDP CID %u...", + ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST, ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback) swwan_disconnect_ready, + task); + g_free (command); + return; + } + + case DISCONNECT_3GPP_CONTEXT_STEP_CONNECTION_STATUS: + mm_obj_dbg (self, "disconnect step %u/%u: checking SWWAN interface %u status...", + ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST, + usb_interface_configs[ctx->usb_interface_config_index].swwan_index); + load_connection_status_by_cid (MM_BROADBAND_BEARER_CINTERION (ctx->self), + (gint) ctx->cid, + TRUE, + FALSE, + (GAsyncReadyCallback) disconnect_connection_status_ready, + task); + return; + + case DISCONNECT_3GPP_CONTEXT_STEP_LAST: + mm_obj_dbg (self, "disconnect step %u/%u: finished", + ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +disconnect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Disconnect3gppContext *ctx; + GError *error = NULL; + + g_assert (primary != NULL); + g_assert (data != NULL); + + /* Setup task and create connection context */ + task = g_task_new (self, NULL, callback, user_data); + ctx = g_slice_new0 (Disconnect3gppContext); + g_task_set_task_data (task, ctx, (GDestroyNotify) disconnect_3gpp_context_free); + + /* Setup context */ + ctx->self = MM_BROADBAND_BEARER_CINTERION (g_object_ref (self)); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + ctx->data = g_object_ref (data); + ctx->cid = cid; + ctx->step = DISCONNECT_3GPP_CONTEXT_STEP_FIRST; + + /* Validate configuration */ + ctx->usb_interface_config_index = get_usb_interface_config_index (data, &error); + if (ctx->usb_interface_config_index < 0) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Start */ + disconnect_3gpp_context_step (task); +} + +/*****************************************************************************/ +/* Setup and Init Bearers */ + +MMBaseBearer * +mm_broadband_bearer_cinterion_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *bearer; + GObject *source; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_cinterion_new (MMBroadbandModemCinterion *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_CINTERION, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + NULL); +} + +static void +mm_broadband_bearer_cinterion_init (MMBroadbandBearerCinterion *self) +{ +} + +static void +mm_broadband_bearer_cinterion_class_init (MMBroadbandBearerCinterionClass *klass) +{ + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + base_bearer_class->load_connection_status = load_connection_status; + base_bearer_class->load_connection_status_finish = load_connection_status_finish; +#if defined WITH_SUSPEND_RESUME + base_bearer_class->reload_connection_status = load_connection_status; + base_bearer_class->reload_connection_status_finish = load_connection_status_finish; +#endif + + broadband_bearer_class->dial_3gpp = dial_3gpp; + broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; +} diff --git a/src/plugins/cinterion/mm-broadband-bearer-cinterion.h b/src/plugins/cinterion/mm-broadband-bearer-cinterion.h new file mode 100644 index 00000000..d514759d --- /dev/null +++ b/src/plugins/cinterion/mm-broadband-bearer-cinterion.h @@ -0,0 +1,54 @@ +/* -*- 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) 2016 Trimble Navigation Limited + * Author: Matthew Stanger <Matthew_Stanger@trimble.com> + */ + +#ifndef MM_BROADBAND_BEARER_CINTERION_H +#define MM_BROADBAND_BEARER_CINTERION_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-cinterion.h" + +#define MM_TYPE_BROADBAND_BEARER_CINTERION (mm_broadband_bearer_cinterion_get_type ()) +#define MM_BROADBAND_BEARER_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterion)) +#define MM_BROADBAND_BEARER_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterionClass)) +#define MM_IS_BROADBAND_BEARER_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION)) +#define MM_IS_BROADBAND_BEARER_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_CINTERION)) +#define MM_BROADBAND_BEARER_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterionClass)) + +typedef struct _MMBroadbandBearerCinterion MMBroadbandBearerCinterion; +typedef struct _MMBroadbandBearerCinterionClass MMBroadbandBearerCinterionClass; + +struct _MMBroadbandBearerCinterion { + MMBroadbandBearer parent; +}; + +struct _MMBroadbandBearerCinterionClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_cinterion_get_type (void); + +void mm_broadband_bearer_cinterion_new (MMBroadbandModemCinterion *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_cinterion_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_BROADBAND_BEARER_CINTERION_H */ diff --git a/src/plugins/cinterion/mm-broadband-modem-cinterion.c b/src/plugins/cinterion/mm-broadband-modem-cinterion.c new file mode 100644 index 00000000..b063d454 --- /dev/null +++ b/src/plugins/cinterion/mm-broadband-modem-cinterion.c @@ -0,0 +1,3356 @@ +/* -*- 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 Ammonit Measurement GmbH + * Copyright (C) 2011 Google Inc. + * Copyright (C) 2016 Trimble Navigation Limited + * Author: Aleksander Morgado <aleksander@lanedo.com> + * Contributor: Matthew Stanger <matthew_stanger@trimble.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-modem-helpers.h" +#include "mm-serial-parsers.h" +#include "mm-log-object.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-messaging.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-cinterion.h" +#include "mm-modem-helpers-cinterion.h" +#include "mm-shared-cinterion.h" +#include "mm-broadband-bearer-cinterion.h" +#include "mm-iface-modem-signal.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_messaging_init (MMIfaceModemMessaging *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_voice_init (MMIfaceModemVoice *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); +static void iface_modem_signal_init (MMIfaceModemSignal *iface); +static void shared_cinterion_init (MMSharedCinterion *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModemLocation *iface_modem_location_parent; +static MMIfaceModemVoice *iface_modem_voice_parent; +static MMIfaceModemTime *iface_modem_time_parent; +static MMIfaceModemSignal *iface_modem_signal_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemCinterion, mm_broadband_modem_cinterion, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init)) + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED, +} FeatureSupport; + +struct _MMBroadbandModemCinterionPrivate { + /* Command to go into sleep mode */ + gchar *sleep_mode_cmd; + + /* Cached supported bands in Cinterion format */ + guint supported_bands[MM_CINTERION_RB_BLOCK_N]; + + /* Cached supported modes for SMS setup */ + GArray *cnmi_supported_mode; + GArray *cnmi_supported_mt; + GArray *cnmi_supported_bm; + GArray *cnmi_supported_ds; + GArray *cnmi_supported_bfr; + + /* Cached supported rats for SXRAT */ + GArray *sxrat_supported_rat; + GArray *sxrat_supported_pref1; + + /* ignore regex */ + GRegex *sysstart_regex; + /* +CIEV indications as configured via AT^SIND */ + GRegex *ciev_regex; + /* Ignore SIM hotswap SCKS msg, until ready */ + GRegex *scks_regex; + + /* Flags for feature support checks */ + FeatureSupport swwan_support; + FeatureSupport sind_psinfo_support; + FeatureSupport smoni_support; + FeatureSupport sind_simstatus_support; + FeatureSupport sxrat_support; + + /* Mode combination to apply if "any" requested */ + MMModemMode any_allowed; + + /* Flags for model-based behaviors */ + MMCinterionModemFamily modem_family; + MMCinterionRadioBandFormat rb_format; + + /* Initial EPS bearer context number */ + gint initial_eps_bearer_cid; +}; + +/*****************************************************************************/ + +MMCinterionModemFamily +mm_broadband_modem_cinterion_get_family (MMBroadbandModemCinterion *self) +{ + return self->priv->modem_family; +} + +/*****************************************************************************/ +/* Check support (Signal interface) */ + +static gboolean +signal_check_support_finish (MMIfaceModemSignal *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_signal_check_support_ready (MMIfaceModemSignal *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_signal_parent->check_support_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +check_smoni_support (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + + /* Fetch the result to the SMONI test. If no response given (error triggered), assume unsupported */ + if (mm_base_modem_at_command_finish (_self, res, NULL)) { + mm_obj_dbg (self, "SMONI supported"); + self->priv->smoni_support = FEATURE_SUPPORTED; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "SMONI unsupported"); + self->priv->smoni_support = FEATURE_NOT_SUPPORTED; + + /* Otherwise, check if the parent CESQ-based implementation works */ + g_assert (iface_modem_signal_parent->check_support && iface_modem_signal_parent->check_support_finish); + iface_modem_signal_parent->check_support (MM_IFACE_MODEM_SIGNAL (self), + (GAsyncReadyCallback) parent_signal_check_support_ready, + task); +} + +static void +signal_check_support (MMIfaceModemSignal *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SMONI=?", + 3, + TRUE, + (GAsyncReadyCallback) check_smoni_support, + task); +} + +/*****************************************************************************/ +/* Load extended signal information (Signal interface) */ + +static gboolean +signal_load_values_finish (MMIfaceModemSignal *_self, + GAsyncResult *res, + MMSignal **cdma, + MMSignal **evdo, + MMSignal **gsm, + MMSignal **umts, + MMSignal **lte, + MMSignal **nr5g, + GError **error) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + + if (self->priv->smoni_support == FEATURE_NOT_SUPPORTED) + return iface_modem_signal_parent->load_values_finish (_self, res, cdma, evdo, gsm, umts, lte, nr5g, error); + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error); + if (!response || !mm_cinterion_smoni_response_to_signal_info (response, gsm, umts, lte, error)) + return FALSE; + + if (cdma) + *cdma = NULL; + if (evdo) + *evdo = NULL; + if (nr5g) + *nr5g = NULL; + + return TRUE; +} + +static void +signal_load_values (MMIfaceModemSignal *_self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + + if (self->priv->smoni_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SMONI", + 3, + FALSE, + callback, + user_data); + return; + } + + /* ^SMONI not supported, fallback to the parent */ + iface_modem_signal_parent->load_values (_self, cancellable, callback, user_data); +} + +/*****************************************************************************/ +/* Enable unsolicited events (SMS indications) (Messaging interface) */ + +static gboolean +messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cnmi_test_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static gboolean +value_supported (const GArray *array, + const guint value) +{ + guint i; + + if (!array) + return FALSE; + + for (i = 0; i < array->len; i++) { + if (g_array_index (array, guint, i) == value) + return TRUE; + } + return FALSE; +} + +static void +messaging_enable_unsolicited_events (MMIfaceModemMessaging *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GString *cmd; + GError *error = NULL; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* AT+CNMI=<mode>,[<mt>[,<bm>[,<ds>[,<bfr>]]]] */ + cmd = g_string_new ("+CNMI="); + + /* Mode 2 or 1 */ + if (value_supported (self->priv->cnmi_supported_mode, 2)) + g_string_append_printf (cmd, "%u,", 2); + else if (value_supported (self->priv->cnmi_supported_mode, 1)) + g_string_append_printf (cmd, "%u,", 1); + else { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "SMS settings don't accept [2,1] <mode>"); + goto out; + } + + /* mt 2 or 1 */ + if (value_supported (self->priv->cnmi_supported_mt, 2)) + g_string_append_printf (cmd, "%u,", 2); + else if (value_supported (self->priv->cnmi_supported_mt, 1)) + g_string_append_printf (cmd, "%u,", 1); + else { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "SMS settings don't accept [2,1] <mt>"); + goto out; + } + + /* bm 2 or 0 */ + if (value_supported (self->priv->cnmi_supported_bm, 2)) + g_string_append_printf (cmd, "%u,", 2); + else if (value_supported (self->priv->cnmi_supported_bm, 0)) + g_string_append_printf (cmd, "%u,", 0); + else { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "SMS settings don't accept [2,0] <bm>"); + goto out; + } + + /* ds 2, 1 or 0 */ + if (value_supported (self->priv->cnmi_supported_ds, 2)) + g_string_append_printf (cmd, "%u,", 2); + else if (value_supported (self->priv->cnmi_supported_ds, 1)) + g_string_append_printf (cmd, "%u,", 1); + else if (value_supported (self->priv->cnmi_supported_ds, 0)) + g_string_append_printf (cmd, "%u,", 0); + else { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "SMS settings don't accept [2,1,0] <ds>"); + goto out; + } + + /* bfr 1 */ + if (value_supported (self->priv->cnmi_supported_bfr, 1)) + g_string_append_printf (cmd, "%u", 1); + /* otherwise, skip setting it */ + +out: + /* Early error report */ + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + g_string_free (cmd, TRUE); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd->str, + 3, + FALSE, + (GAsyncReadyCallback)cnmi_test_ready, + task); + g_string_free (cmd, TRUE); +} + +/*****************************************************************************/ +/* Check if Messaging supported (Messaging interface) */ + +static gboolean +messaging_check_support_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cnmi_format_check_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Parse */ + if (!mm_cinterion_parse_cnmi_test (response, + &self->priv->cnmi_supported_mode, + &self->priv->cnmi_supported_mt, + &self->priv->cnmi_supported_bm, + &self->priv->cnmi_supported_ds, + &self->priv->cnmi_supported_bfr, + &error)) { + mm_obj_warn (self, "error reading SMS setup: %s", error->message); + g_error_free (error); + } + + /* CNMI command is supported; assume we have full messaging capabilities */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +messaging_check_support (MMIfaceModemMessaging *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* We assume that CDMA-only modems don't have messaging capabilities */ + if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "CDMA-only modems don't have messaging capabilities"); + g_object_unref (task); + return; + } + + /* Check CNMI support */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CNMI=?", + 3, + TRUE, + (GAsyncReadyCallback)cnmi_format_check_ready, + task); +} + +/*****************************************************************************/ +/* Power down */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +sleep_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) + mm_obj_dbg (self, "couldn't send power down command: %s", error->message); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +send_sleep_mode_command (GTask *task) +{ + MMBroadbandModemCinterion *self; + + self = g_task_get_source_object (task); + + if (self->priv->sleep_mode_cmd && self->priv->sleep_mode_cmd[0]) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + self->priv->sleep_mode_cmd, + 5, + FALSE, + (GAsyncReadyCallback)sleep_ready, + task); + return; + } + + /* No default command; just finish without sending anything */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +supported_functionality_status_query_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + g_autoptr(GError) error = NULL; + + g_assert (self->priv->sleep_mode_cmd == NULL); + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) { + mm_obj_warn (self, "couldn't query supported functionality status: %s", error->message); + self->priv->sleep_mode_cmd = g_strdup (""); + } else { + /* We need to get which power-off command to use to put the modem in low + * power mode (with serial port open for AT commands, but with RF switched + * off). According to the documentation of various Cinterion modems, some + * support AT+CFUN=4 (HC25) and those which don't support it can use + * AT+CFUN=7 (CYCLIC SLEEP mode with 2s timeout after last character + * received in the serial port). + * + * So, just look for '4' in the reply; if not found, look for '7', and if + * not found, report warning and don't use any. + */ + if (strstr (response, "4") != NULL) { + mm_obj_dbg (self, "device supports CFUN=4 sleep mode"); + self->priv->sleep_mode_cmd = g_strdup ("+CFUN=4"); + } else if (strstr (response, "7") != NULL) { + mm_obj_dbg (self, "device supports CFUN=7 sleep mode"); + self->priv->sleep_mode_cmd = g_strdup ("+CFUN=7"); + } else { + mm_obj_warn (self, "unknown functionality mode to go into sleep mode"); + self->priv->sleep_mode_cmd = g_strdup (""); + } + } + + send_sleep_mode_command (task); +} + +static void +modem_power_down (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* If sleep command already decided, use it. */ + if (self->priv->sleep_mode_cmd) + send_sleep_mode_command (task); + else + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CFUN=?", + 3, + FALSE, + (GAsyncReadyCallback)supported_functionality_status_query_ready, + task); +} + +/*****************************************************************************/ +/* Modem Power Off */ + +#define MAX_POWER_OFF_WAIT_TIME_SECS 20 + +typedef struct { + MMPortSerialAt *port; + GRegex *shutdown_regex; + gboolean shutdown_received; + gboolean smso_replied; + gboolean serial_open; + guint timeout_id; +} PowerOffContext; + +static void +power_off_context_free (PowerOffContext *ctx) +{ + if (ctx->serial_open) + mm_port_serial_close (MM_PORT_SERIAL (ctx->port)); + if (ctx->timeout_id) + g_source_remove (ctx->timeout_id); + mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->shutdown_regex, NULL, NULL, NULL); + g_object_unref (ctx->port); + g_regex_unref (ctx->shutdown_regex); + g_slice_free (PowerOffContext, ctx); +} + +static gboolean +modem_power_off_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +complete_power_off (GTask *task) +{ + PowerOffContext *ctx; + + ctx = g_task_get_task_data (task); + + if (!ctx->shutdown_received || !ctx->smso_replied) + return; + + /* remove timeout right away */ + g_assert (ctx->timeout_id); + g_source_remove (ctx->timeout_id); + ctx->timeout_id = 0; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +smso_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + PowerOffContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Set as replied and see if we can complete */ + ctx->smso_replied = TRUE; + complete_power_off (task); +} + +static void +shutdown_received (MMPortSerialAt *port, + GMatchInfo *match_info, + GTask *task) +{ + PowerOffContext *ctx; + + ctx = g_task_get_task_data (task); + + /* Cleanup handler right away, we don't want it called any more */ + mm_port_serial_at_add_unsolicited_msg_handler (port, ctx->shutdown_regex, NULL, NULL, NULL); + + /* Set as received and see if we can complete */ + ctx->shutdown_received = TRUE; + complete_power_off (task); +} + +static gboolean +power_off_timeout_cb (GTask *task) +{ + PowerOffContext *ctx; + + ctx = g_task_get_task_data (task); + + ctx->timeout_id = 0; + + /* The SMSO reply should have come earlier */ + g_warn_if_fail (ctx->smso_replied == TRUE); + + /* Cleanup handler right away, we no longer want to receive it */ + mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->shutdown_regex, NULL, NULL, NULL); + + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Power off operation timed out"); + g_object_unref (task); + + return G_SOURCE_REMOVE; +} + +static void +modem_power_off (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + PowerOffContext *ctx; + GError *error = NULL; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_slice_new0 (PowerOffContext); + ctx->port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); + ctx->shutdown_regex = g_regex_new ("\\r\\n\\^SHUTDOWN\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + ctx->timeout_id = g_timeout_add_seconds (MAX_POWER_OFF_WAIT_TIME_SECS, + (GSourceFunc)power_off_timeout_cb, + task); + g_task_set_task_data (task, ctx, (GDestroyNotify) power_off_context_free); + + /* We'll need to wait for a ^SHUTDOWN before returning the action, which is + * when the modem tells us that it is ready to be shutdown */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ctx->port, + ctx->shutdown_regex, + (MMPortSerialAtUnsolicitedMsgFn)shutdown_received, + task, + NULL); + + /* In order to get the ^SHUTDOWN notification, we must keep the port open + * during the wait time */ + ctx->serial_open = mm_port_serial_open (MM_PORT_SERIAL (ctx->port), &error); + if (G_UNLIKELY (error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Note: we'll use a timeout < MAX_POWER_OFF_WAIT_TIME_SECS for the AT command, + * so we're sure that the AT command reply will always come before the timeout + * fires */ + g_assert (MAX_POWER_OFF_WAIT_TIME_SECS > 5); + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + ctx->port, + "^SMSO", + 5, + FALSE, /* allow_cached */ + FALSE, /* is_raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)smso_ready, + task); +} + +/*****************************************************************************/ +/* Access technologies polling */ + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + GError *inner_error = NULL; + gssize val; + + val = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + *access_technologies = (MMModemAccessTechnology) val; + *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; + return TRUE; +} + +static void +smong_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + MMModemAccessTechnology access_tech; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response || !mm_cinterion_parse_smong_response (response, &access_tech, &error)) + g_task_return_error (task, error); + else + g_task_return_int (task, (gssize) access_tech); + g_object_unref (task); +} + +static void +load_access_technologies (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Abort access technology polling if ^SIND psinfo URCs are enabled */ + if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "No need to poll access technologies"); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "^SMONG", + 3, + FALSE, + (GAsyncReadyCallback)smong_query_ready, + task); +} + +/*****************************************************************************/ +/* Disable unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "couldn't disable parent 3GPP unsolicited events: %s", error->message); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_disable_unsolicited_messages (GTask *task) +{ + /* Chain up parent's disable */ + iface_modem_3gpp_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (g_task_get_source_object (task)), + (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, + task); +} + +static void +sind_psinfo_disable_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + mm_obj_warn (self, "Couldn't disable ^SIND psinfo notifications: %s", error->message); + + parent_disable_unsolicited_messages (task); +} + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self; + GTask *task; + + self = MM_BROADBAND_MODEM_CINTERION (_self); + + task = g_task_new (self, NULL, callback, user_data); + + if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) { + /* Disable access technology update reporting */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SIND=\"psinfo\",0", + 3, + FALSE, + (GAsyncReadyCallback)sind_psinfo_disable_ready, + task); + return; + } + + parent_disable_unsolicited_messages (task); +} + +/*****************************************************************************/ +/* Enable unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +sind_psinfo_enable_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self; + g_autoptr(GError) error = NULL; + const gchar *response; + guint mode; + guint val; + + self = MM_BROADBAND_MODEM_CINTERION (_self); + if (!(response = mm_base_modem_at_command_finish (_self, res, &error))) { + /* something went wrong, disable indicator */ + self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED; + mm_obj_warn (self, "couldn't enable ^SIND psinfo notifications: %s", error->message); + } else if (!mm_cinterion_parse_sind_response (response, NULL, &mode, &val, &error)) { + /* problem with parsing, disable indicator */ + self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED; + mm_obj_warn (self, "couldn't parse ^SIND psinfo response: %s", error->message); + } else { + /* Report initial access technology gathered right away */ + mm_obj_dbg (self, "reporting initial access technologies..."); + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + mm_cinterion_get_access_technology_from_sind_psinfo (val, self), + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +set_urc_dest_port_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self; + g_autoptr(GError) error = NULL; + + self = MM_BROADBAND_MODEM_CINTERION (_self); + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, &error)) + mm_obj_dbg (self, "couldn't guarantee unsolicited events are sent to the correct port: %s", error->message); + + if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) { + /* Enable access technology update reporting */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SIND=\"psinfo\",1", + 3, + FALSE, + (GAsyncReadyCallback)sind_psinfo_enable_ready, + task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "couldn't enable parent 3GPP unsolicited events: %s", error->message); + + /* Make sure unsolicited events are sent to an AT port (PLS9 can default to DATA port) */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SCFG=\"URC/DstIfc\",\"app\"", + 5, + FALSE, + (GAsyncReadyCallback)set_urc_dest_port_ready, + task); +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's enable */ + iface_modem_3gpp_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static void +sind_ciev_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemCinterion *self) +{ + guint val = 0; + gchar *indicator; + + indicator = mm_get_string_unquoted_from_match_info (match_info, 1); + if (!mm_get_uint_from_match_info (match_info, 2, &val)) + mm_obj_dbg (self, "couldn't parse indicator '%s' value", indicator); + else { + mm_obj_dbg (self, "received indicator '%s' update: %u", indicator, val); + if (g_strcmp0 (indicator, "psinfo") == 0) { + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + mm_cinterion_get_access_technology_from_sind_psinfo (val, self), + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); + } + } + g_free (indicator); +} + +static void +set_unsolicited_events_handlers (MMBroadbandModemCinterion *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->ciev_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)sind_ciev_received : NULL, + enable ? self : NULL, + NULL); + } +} + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_CINTERION (self), TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, + task); +} + +static void +parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Our own cleanup first */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_CINTERION (self), FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Common operation to load expected CID for the initial EPS bearer */ + +static gboolean +load_initial_eps_bearer_cid_finish (MMBroadbandModemCinterion *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +scfg_prov_cfg_query_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + g_autoptr(GError) error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) + mm_obj_dbg (self, "couldn't query MNO profiles: %s", error->message); + + else if (!mm_cinterion_provcfg_response_to_cid (response, + MM_BROADBAND_MODEM_CINTERION (self)->priv->modem_family, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + self, + &self->priv->initial_eps_bearer_cid, + &error)) + mm_obj_dbg (self, "failed processing list of MNO profiles: %s", error->message); + + if (self->priv->initial_eps_bearer_cid < 0) { + mm_obj_dbg (self, "using default EPS bearer context id: 1"); + self->priv->initial_eps_bearer_cid = 1; + } else + mm_obj_dbg (self, "loaded EPS bearer context id from list of MNO profiles: %d", self->priv->initial_eps_bearer_cid); + + /* This operation really never fails */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +load_initial_eps_bearer_cid (MMBroadbandModemCinterion *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + g_assert (self->priv->initial_eps_bearer_cid < 0); + + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SCFG=\"MEopMode/Prov/Cfg\"", + 20, + FALSE, + (GAsyncReadyCallback)scfg_prov_cfg_query_ready, + task); +} + +/*****************************************************************************/ +/* Set initial EPS bearer settings */ + +typedef enum { + SET_INITIAL_EPS_STEP_FIRST = 0, + SET_INITIAL_EPS_STEP_CHECK_MODE, + SET_INITIAL_EPS_STEP_RF_OFF, + SET_INITIAL_EPS_STEP_APN, + SET_INITIAL_EPS_STEP_AUTH, + SET_INITIAL_EPS_STEP_RF_ON, + SET_INITIAL_EPS_STEP_LAST, +} SetInitialEpsStep; + +typedef struct { + MMBearerProperties *properties; + SetInitialEpsStep step; + guint initial_cfun_mode; + GError *saved_error; +} SetInitialEpsContext; + +static void +set_initial_eps_context_free (SetInitialEpsContext *ctx) +{ + g_assert (!ctx->saved_error); + g_object_unref (ctx->properties); + g_slice_free (SetInitialEpsContext, ctx); +} + +static gboolean +modem_3gpp_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void set_initial_eps_step (GTask *task); + +static void +set_initial_eps_rf_on_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + SetInitialEpsContext *ctx; + + ctx = (SetInitialEpsContext *) g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't set RF back on: %s", error->message); + if (!ctx->saved_error) + ctx->saved_error = g_steal_pointer (&error); + } + + /* Go to next step */ + ctx->step++; + set_initial_eps_step (task); +} + +static void +set_initial_eps_auth_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + SetInitialEpsContext *ctx; + + ctx = (SetInitialEpsContext *) g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (_self, res, &ctx->saved_error)) { + mm_obj_warn (self, "couldn't configure context %d auth settings: %s", + self->priv->initial_eps_bearer_cid, ctx->saved_error->message); + /* Fallback to recover RF before returning the error */ + ctx->step = SET_INITIAL_EPS_STEP_RF_ON; + } else { + /* Go to next step */ + ctx->step++; + } + set_initial_eps_step (task); +} + +static void +set_initial_eps_cgdcont_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + SetInitialEpsContext *ctx; + + ctx = (SetInitialEpsContext *) g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (_self, res, &ctx->saved_error)) { + mm_obj_warn (self, "couldn't configure context %d settings: %s", + self->priv->initial_eps_bearer_cid, ctx->saved_error->message); + /* Fallback to recover RF before returning the error */ + ctx->step = SET_INITIAL_EPS_STEP_RF_ON; + } else { + /* Go to next step */ + ctx->step++; + } + set_initial_eps_step (task); +} + +static void +set_initial_eps_rf_off_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + SetInitialEpsContext *ctx; + + ctx = (SetInitialEpsContext *) g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't set RF off: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go to next step */ + ctx->step++; + set_initial_eps_step (task); +} + +static void +set_initial_eps_cfun_mode_load_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + SetInitialEpsContext *ctx; + guint mode; + + ctx = (SetInitialEpsContext *) g_task_get_task_data (task); + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response || !mm_3gpp_parse_cfun_query_response (response, &mode, &error)) { + mm_obj_warn (self, "couldn't load initial functionality mode: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "current functionality mode: %u", mode); + if (mode != 1 && mode != 4) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "cannot setup the default LTE bearer settings: " + "the SIM must be powered"); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx->initial_cfun_mode = mode; + ctx->step++; + set_initial_eps_step (task); +} + +static void +set_initial_eps_step (GTask *task) +{ + MMBroadbandModemCinterion *self; + SetInitialEpsContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case SET_INITIAL_EPS_STEP_FIRST: + ctx->step++; + /* fall through */ + + case SET_INITIAL_EPS_STEP_CHECK_MODE: + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CFUN?", + 5, + FALSE, + (GAsyncReadyCallback)set_initial_eps_cfun_mode_load_ready, + task); + return; + + case SET_INITIAL_EPS_STEP_RF_OFF: + if (ctx->initial_cfun_mode != 4) { + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CFUN=4", + 5, + FALSE, + (GAsyncReadyCallback)set_initial_eps_rf_off_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case SET_INITIAL_EPS_STEP_APN: { + const gchar *apn; + g_autofree gchar *quoted_apn = NULL; + g_autofree gchar *apn_cmd = NULL; + const gchar *ip_family_str; + MMBearerIpFamily ip_family; + + ip_family = mm_bearer_properties_get_ip_type (ctx->properties); + if (ip_family == MM_BEARER_IP_FAMILY_NONE || ip_family == MM_BEARER_IP_FAMILY_ANY) + ip_family = MM_BEARER_IP_FAMILY_IPV4; + + ip_family_str = mm_3gpp_get_pdp_type_from_ip_family (ip_family); + apn = mm_bearer_properties_get_apn (ctx->properties); + mm_obj_dbg (self, "context %d with APN '%s' and PDP type '%s'", + self->priv->initial_eps_bearer_cid, apn, ip_family_str); + quoted_apn = mm_port_serial_at_quote_string (apn); + apn_cmd = g_strdup_printf ("+CGDCONT=%u,\"%s\",%s", + self->priv->initial_eps_bearer_cid, ip_family_str, quoted_apn); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + apn_cmd, + 20, + FALSE, + (GAsyncReadyCallback)set_initial_eps_cgdcont_ready, + task); + return; + } + + case SET_INITIAL_EPS_STEP_AUTH: { + g_autofree gchar *auth_cmd = NULL; + + auth_cmd = mm_cinterion_build_auth_string (self, + MM_BROADBAND_MODEM_CINTERION (self)->priv->modem_family, + ctx->properties, + self->priv->initial_eps_bearer_cid); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + auth_cmd, + 20, + FALSE, + (GAsyncReadyCallback)set_initial_eps_auth_ready, + task); + return; + } + + case SET_INITIAL_EPS_STEP_RF_ON: + if (ctx->initial_cfun_mode == 1) { + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CFUN=1", + 5, + FALSE, + (GAsyncReadyCallback)set_initial_eps_rf_on_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case SET_INITIAL_EPS_STEP_LAST: + if (ctx->saved_error) + g_task_return_error (task, g_steal_pointer (&ctx->saved_error)); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +modem_3gpp_set_initial_eps_bearer_settings (MMIfaceModem3gpp *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + SetInitialEpsContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + + /* The initial EPS bearer settings should have already been loaded */ + g_assert (MM_BROADBAND_MODEM_CINTERION (self)->priv->initial_eps_bearer_cid >= 0); + + /* Setup context */ + ctx = g_slice_new0 (SetInitialEpsContext); + ctx->properties = g_object_ref (properties); + ctx->step = SET_INITIAL_EPS_STEP_FIRST; + g_task_set_task_data (task, ctx, (GDestroyNotify) set_initial_eps_context_free); + + set_initial_eps_step (task); +} + +/*****************************************************************************/ +/* Common initial EPS bearer info loading for both: + * - runtime status + * - configuration settings + */ + +typedef enum { + COMMON_LOAD_INITIAL_EPS_STEP_FIRST = 0, + COMMON_LOAD_INITIAL_EPS_STEP_PROFILE, + COMMON_LOAD_INITIAL_EPS_STEP_APN, + COMMON_LOAD_INITIAL_EPS_STEP_AUTH, + COMMON_LOAD_INITIAL_EPS_STEP_LAST, +} CommonLoadInitialEpsStep; + +typedef struct { + MMBearerProperties *properties; + CommonLoadInitialEpsStep step; + gboolean runtime; +} CommonLoadInitialEpsContext; + +static void +common_load_initial_eps_context_free (CommonLoadInitialEpsContext *ctx) +{ + g_clear_object (&ctx->properties); + g_slice_free (CommonLoadInitialEpsContext, ctx); +} + +static MMBearerProperties * +common_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error)); +} + +static void common_load_initial_eps_step (GTask *task); + +static void +common_load_initial_eps_auth_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + CommonLoadInitialEpsContext *ctx; + g_autoptr(GError) error = NULL; + MMBearerAllowedAuth auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN; + g_autofree gchar *username = NULL; + + ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) + mm_obj_dbg (self, "couldn't load context %d auth settings: %s", + self->priv->initial_eps_bearer_cid, error->message); + else if (!mm_cinterion_parse_sgauth_response (response, self->priv->initial_eps_bearer_cid, &auth, &username, &error)) + mm_obj_dbg (self, "couldn't parse context %d auth settings: %s", self->priv->initial_eps_bearer_cid, error->message); + else { + mm_bearer_properties_set_allowed_auth (ctx->properties, auth); + mm_bearer_properties_set_user (ctx->properties, username); + } + + /* Go to next step */ + ctx->step++; + common_load_initial_eps_step (task); +} + +static void +common_load_initial_eps_load_cid_ready (MMBroadbandModemCinterion *self, + GAsyncResult *res, + GTask *task) +{ + CommonLoadInitialEpsContext *ctx; + + ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task); + + load_initial_eps_bearer_cid_finish (self, res, NULL); + g_assert (self->priv->initial_eps_bearer_cid >= 0); + + /* Go to next step */ + ctx->step++; + common_load_initial_eps_step (task); +} + +static void +common_load_initial_eps_cgcontrdp_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + CommonLoadInitialEpsContext *ctx; + g_autofree gchar *apn = NULL; + g_autoptr(GError) error = NULL; + + ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task); + + /* errors aren't fatal */ + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) + mm_obj_dbg (self, "couldn't load context %d settings: %s", + self->priv->initial_eps_bearer_cid, error->message); + else if (!mm_3gpp_parse_cgcontrdp_response (response, NULL, NULL, &apn, NULL, NULL, NULL, NULL, NULL, &error)) + mm_obj_dbg (self, "couldn't parse CGDCONTRDP response: %s", error->message); + else + mm_bearer_properties_set_apn (ctx->properties, apn); + + /* Go to next step */ + ctx->step++; + common_load_initial_eps_step (task); +} + +static void +common_load_initial_eps_cgdcont_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + CommonLoadInitialEpsContext *ctx; + g_autoptr(GError) error = NULL; + + ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task); + + /* errors aren't fatal */ + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) + mm_obj_dbg (self, "couldn't load context %d status: %s", + self->priv->initial_eps_bearer_cid, error->message); + else { + GList *context_list; + + context_list = mm_3gpp_parse_cgdcont_read_response (response, &error); + if (!context_list) + mm_obj_dbg (self, "couldn't parse CGDCONT response: %s", error->message); + else { + GList *l; + + for (l = context_list; l; l = g_list_next (l)) { + MM3gppPdpContext *pdp = l->data; + + if (pdp->cid == (guint) self->priv->initial_eps_bearer_cid) { + mm_bearer_properties_set_ip_type (ctx->properties, pdp->pdp_type); + mm_bearer_properties_set_apn (ctx->properties, pdp->apn ? pdp->apn : ""); + break; + } + } + if (!l) + mm_obj_dbg (self, "no status reported for context %d", self->priv->initial_eps_bearer_cid); + mm_3gpp_pdp_context_list_free (context_list); + } + } + + /* Go to next step */ + ctx->step++; + common_load_initial_eps_step (task); +} + +static void +common_load_initial_eps_step (GTask *task) +{ + MMBroadbandModemCinterion *self; + CommonLoadInitialEpsContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case COMMON_LOAD_INITIAL_EPS_STEP_FIRST: + ctx->step++; + /* fall through */ + + case COMMON_LOAD_INITIAL_EPS_STEP_PROFILE: + /* Initial EPS bearer CID initialization run once only */ + if (G_UNLIKELY (self->priv->initial_eps_bearer_cid < 0)) { + load_initial_eps_bearer_cid ( + self, + (GAsyncReadyCallback)common_load_initial_eps_load_cid_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case COMMON_LOAD_INITIAL_EPS_STEP_APN: + if (ctx->runtime) { + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CGDCONT?", + 20, + FALSE, + (GAsyncReadyCallback)common_load_initial_eps_cgdcont_ready, + task); + } else { + g_autofree gchar *cmd = NULL; + + cmd = g_strdup_printf ("+CGCONTRDP=%u", self->priv->initial_eps_bearer_cid); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CGCONTRDP", + 20, + FALSE, + (GAsyncReadyCallback)common_load_initial_eps_cgcontrdp_ready, + task); + } + return; + + case COMMON_LOAD_INITIAL_EPS_STEP_AUTH: + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "^SGAUTH?", + 20, + FALSE, + (GAsyncReadyCallback)common_load_initial_eps_auth_ready, + task); + return; + + case COMMON_LOAD_INITIAL_EPS_STEP_LAST: + g_task_return_pointer (task, g_steal_pointer (&ctx->properties), g_object_unref); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +common_load_initial_eps_bearer (MMIfaceModem3gpp *self, + gboolean runtime, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + CommonLoadInitialEpsContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + + /* Setup context */ + ctx = g_slice_new0 (CommonLoadInitialEpsContext); + ctx->runtime = runtime; + ctx->properties = mm_bearer_properties_new (); + ctx->step = COMMON_LOAD_INITIAL_EPS_STEP_FIRST; + g_task_set_task_data (task, ctx, (GDestroyNotify) common_load_initial_eps_context_free); + + common_load_initial_eps_step (task); +} + +/*****************************************************************************/ +/* Initial EPS bearer runtime status loading */ + +static MMBearerProperties * +modem_3gpp_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return common_load_initial_eps_bearer_finish (self, res, error); +} + +static void +modem_3gpp_load_initial_eps_bearer (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_load_initial_eps_bearer (self, TRUE, callback, user_data); +} + +/*****************************************************************************/ +/* Initial EPS bearer settings loading -> set configuration */ + +static MMBearerProperties * +modem_3gpp_load_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return common_load_initial_eps_bearer_finish (self, res, error); +} + +static void +modem_3gpp_load_initial_eps_bearer_settings (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_load_initial_eps_bearer (self, FALSE, callback, user_data); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMModemModeCombination mode; + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + if (mm_iface_modem_is_4g (self)) { + /* 4G only */ + mode.allowed = MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G, 3G and 4G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } else { + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +sxrat_load_supported_modes_ready (MMBroadbandModemCinterion *self, + GTask *task) +{ + GArray *combinations; + MMModemModeCombination mode; + + g_assert (self->priv->sxrat_supported_rat); + g_assert (self->priv->sxrat_supported_pref1); + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3); + + if (value_supported (self->priv->sxrat_supported_rat, 0)) { + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_rat, 1)) { + /* 2G+3G with none preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + self->priv->any_allowed = mode.allowed; + + if (value_supported (self->priv->sxrat_supported_pref1, 0)) { + /* 2G preferred */ + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_pref1, 2)) { + /* 3G preferred */ + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + } + } + if (value_supported (self->priv->sxrat_supported_rat, 2)) { + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_rat, 3)) { + /* 4G only */ + mode.allowed = MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_rat, 4)) { + /* 3G+4G with none preferred */ + mode.allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + self->priv->any_allowed = mode.allowed; + + if (value_supported (self->priv->sxrat_supported_pref1, 2)) { + /* 3G preferred */ + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_pref1, 3)) { + /* 4G preferred */ + mode.preferred = MM_MODEM_MODE_4G; + g_array_append_val (combinations, mode); + } + } + if (value_supported (self->priv->sxrat_supported_rat, 5)) { + /* 2G+4G with none preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + self->priv->any_allowed = mode.allowed; + + if (value_supported (self->priv->sxrat_supported_pref1, 0)) { + /* 2G preferred */ + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_pref1, 3)) { + /* 4G preferred */ + mode.preferred = MM_MODEM_MODE_4G; + g_array_append_val (combinations, mode); + } + } + if (value_supported (self->priv->sxrat_supported_rat, 6)) { + /* 2G+3G+4G with none preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + self->priv->any_allowed = mode.allowed; + + if (value_supported (self->priv->sxrat_supported_pref1, 0)) { + /* 2G preferred */ + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_pref1, 2)) { + /* 3G preferred */ + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_pref1, 3)) { + /* 4G preferred */ + mode.preferred = MM_MODEM_MODE_4G; + g_array_append_val (combinations, mode); + } + } + + g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +sxrat_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + g_autoptr(GError) error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!error) { + mm_cinterion_parse_sxrat_test (response, + &self->priv->sxrat_supported_rat, + &self->priv->sxrat_supported_pref1, + NULL, + &error); + if (!error) { + self->priv->sxrat_support = FEATURE_SUPPORTED; + sxrat_load_supported_modes_ready (self, task); + return; + } + mm_obj_warn (self, "error reading SXRAT response: %s", error->message); + } + + self->priv->sxrat_support = FEATURE_NOT_SUPPORTED; + + /* Run parent's loading in case SXRAT is not supported */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + task); +} + +static void +load_supported_modes (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* First check SXRAT support, if not already done */ + if (self->priv->sxrat_support == FEATURE_SUPPORT_UNKNOWN) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SXRAT=?", + 3, + TRUE, + (GAsyncReadyCallback)sxrat_test_ready, + task); + return; + } + + if (self->priv->sxrat_support == FEATURE_SUPPORTED) { + sxrat_load_supported_modes_ready (self, task); + return; + } + + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + task); +} + +/*****************************************************************************/ +/* Set current modes (Modem interface) */ + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +set_current_modes_reregister_in_network_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_iface_modem_3gpp_reregister_in_network_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +allowed_access_technology_update_ready (MMBroadbandModemCinterion *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +cops_set_current_modes (MMBroadbandModemCinterion *self, + MMModemMode allowed, + MMModemMode preferred, + GTask *task) +{ + gchar *command; + + g_assert (preferred == MM_MODEM_MODE_NONE); + + /* We will try to simulate the possible allowed modes here. The + * Cinterion devices do not seem to allow setting preferred access + * technology in devices, but they allow restricting to a given + * one: + * - 2G-only is forced by forcing GERAN RAT (AcT=0) + * - 3G-only is forced by forcing UTRAN RAT (AcT=2) + * - 4G-only is forced by forcing E-UTRAN RAT (AcT=7) + * - for the remaining ones, we default to automatic selection of RAT, + * which is based on the quality of the connection. + */ + + if (mm_iface_modem_is_4g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_4G) + command = g_strdup ("+COPS=,,,7"); + else if (mm_iface_modem_is_3g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_3G) + command = g_strdup ("+COPS=,,,2"); + else if (mm_iface_modem_is_2g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_2G) + command = g_strdup ("+COPS=,,,0"); + else { + /* For any other combination (e.g. ANY or no AcT given, defaults to Auto. For this case, we cannot provide + * AT+COPS=,,, (i.e. just without a last value). Instead, we need to + * re-run the last manual/automatic selection command which succeeded, + * (or auto by default if none was launched) */ + mm_iface_modem_3gpp_reregister_in_network (MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback) set_current_modes_reregister_in_network_ready, + task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 20, + FALSE, + (GAsyncReadyCallback)allowed_access_technology_update_ready, + task); + + g_free (command); +} + +static void +sxrat_set_current_modes (MMBroadbandModemCinterion *self, + MMModemMode allowed, + MMModemMode preferred, + GTask *task) +{ + gchar *command; + GError *error = NULL; + + g_assert (self->priv->any_allowed != MM_MODEM_MODE_NONE); + + /* Handle ANY */ + if (allowed == MM_MODEM_MODE_ANY) + allowed = self->priv->any_allowed; + + command = mm_cinterion_build_sxrat_set_command (allowed, preferred, &error); + + if (!command) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 30, + FALSE, + (GAsyncReadyCallback)allowed_access_technology_update_ready, + task); + + g_free (command); +} + +static void +set_current_modes (MMIfaceModem *_self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + if (self->priv->sxrat_support == FEATURE_SUPPORTED) + sxrat_set_current_modes (self, allowed, preferred, task); + else if (self->priv->sxrat_support == FEATURE_NOT_SUPPORTED) + cops_set_current_modes (self, allowed, preferred, task); + else + g_assert_not_reached (); +} + +/*****************************************************************************/ +/* Supported bands (Modem interface) */ + +static GArray * +load_supported_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +scfg_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + GError *error = NULL; + GArray *bands; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response || + !mm_cinterion_parse_scfg_test (response, + self->priv->modem_family, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + &bands, + &self->priv->rb_format, + &error)) + g_task_return_error (task, error); + else { + if (!mm_cinterion_build_band (bands, + NULL, + FALSE, + self->priv->rb_format, + self->priv->modem_family, + self->priv->supported_bands, + &error)) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); + } + g_object_unref (task); +} + +static void +load_supported_bands (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + MMPort *primary; + MMKernelDevice *port; + const gchar *family = NULL; + + /* Lookup for the tag specifying which modem family the current device belongs */ + primary = MM_PORT (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self))); + port = mm_port_peek_kernel_device (primary); + family = mm_kernel_device_get_global_property (port, "ID_MM_CINTERION_MODEM_FAMILY"); + + /* if the property is not set, default family */ + self->priv->modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT; + + /* set used family also in the string for mm_obj_dbg */ + if (!family) + family = "default"; + + if (g_ascii_strcasecmp (family, "imt") == 0) + self->priv->modem_family = MM_CINTERION_MODEM_FAMILY_IMT; + else if (g_ascii_strcasecmp (family, "default") != 0) { + mm_obj_dbg (self, "cinterion modem family '%s' unknown", family); + family = "default"; + } + + mm_obj_dbg (self, "Using cinterion %s modem family", family); + + task = g_task_new (_self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (_self), + "AT^SCFG=?", + 3, + FALSE, + (GAsyncReadyCallback)scfg_test_ready, + task); +} + +/*****************************************************************************/ +/* Load current bands (Modem interface) */ + +static GArray * +load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +get_band_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + GError *error = NULL; + GArray *bands = NULL; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response || + !mm_cinterion_parse_scfg_response (response, + self->priv->modem_family, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + &bands, + self->priv->rb_format, + &error)) + 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 (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* The timeout in this command is extremely large, because there are some + * modules like the EGS5 that build the response based on the current network + * registration, and that implies the module needs to be registered. If for + * any reason there is no serving network where to register, the response + * comes after a very long time, up to 100s. */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SCFG?", + 120, + FALSE, + (GAsyncReadyCallback)get_band_ready, + task); +} + +/*****************************************************************************/ +/* Set current bands (Modem interface) */ + +typedef struct { + MMBaseModemAtCommandAlloc *cmds; +} SetCurrentBandsContext; + +static void +set_current_bands_context_free (SetCurrentBandsContext *ctx) +{ + if (ctx->cmds) { + guint i; + + for (i = 0; ctx->cmds[i].command; i++) + mm_base_modem_at_command_alloc_clear (&ctx->cmds[i]); + g_free (ctx->cmds); + } + g_slice_free (SetCurrentBandsContext, ctx); +} + +static gboolean +set_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +scfg_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +scfg_set_ready_sequence (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_finish (self, res, NULL, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +set_bands_3g (GTask *task, + GArray *bands_array) +{ + MMBroadbandModemCinterion *self; + GError *error = NULL; + guint band[MM_CINTERION_RB_BLOCK_N] = { 0 }; + + self = g_task_get_source_object (task); + + if (!mm_cinterion_build_band (bands_array, + self->priv->supported_bands, + FALSE, /* 2G and 3G */ + self->priv->rb_format, + self->priv->modem_family, + band, + &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (self->priv->rb_format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) { + g_autofree gchar *cmd = NULL; + + /* Following the setup: + * AT^SCFG="Radion/Band",<rba> + * We will set the preferred band equal to the allowed band, so that we force + * the modem to connect at that specific frequency only. Note that we will be + * passing a number here! + * + * The optional <rbe> field is set to 1, so that changes take effect + * immediately. + */ + cmd = g_strdup_printf ("^SCFG=\"Radio/Band\",%u,1", band[MM_CINTERION_RB_BLOCK_LEGACY]); + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 15, + FALSE, + (GAsyncReadyCallback)scfg_set_ready, + task); + return; + } + + if (self->priv->rb_format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE) { + SetCurrentBandsContext *ctx; + + ctx = g_slice_new0 (SetCurrentBandsContext); + g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_bands_context_free); + + if (self->priv->modem_family == MM_CINTERION_MODEM_FAMILY_IMT) { + g_autofree gchar *bandstr2G = NULL; + g_autofree gchar *bandstr3G = NULL; + g_autofree gchar *bandstr4G = NULL; + g_autofree gchar *bandstr2G_enc = NULL; + g_autofree gchar *bandstr3G_enc = NULL; + g_autofree gchar *bandstr4G_enc = NULL; + + bandstr2G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_GSM]); + bandstr3G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_UMTS]); + bandstr4G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_LTE_LOW]); + + bandstr2G_enc = mm_modem_charset_str_from_utf8 (bandstr2G, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + FALSE, + &error); + if (!bandstr2G_enc) { + g_prefix_error (&error, "Couldn't convert 2G band string to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + bandstr3G_enc = mm_modem_charset_str_from_utf8 (bandstr3G, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + FALSE, + &error); + if (!bandstr3G_enc) { + g_prefix_error (&error, "Couldn't convert 3G band string to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + bandstr4G_enc = mm_modem_charset_str_from_utf8 (bandstr4G, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + FALSE, + &error); + if (!bandstr4G_enc) { + g_prefix_error (&error, "Couldn't convert 4G band string to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, 3 + 1); + ctx->cmds[0].command = g_strdup_printf ("^SCFG=\"Radio/Band/2G\",\"%s\"", bandstr2G_enc); + ctx->cmds[1].command = g_strdup_printf ("^SCFG=\"Radio/Band/3G\",\"%s\"", bandstr3G_enc); + ctx->cmds[2].command = g_strdup_printf ("^SCFG=\"Radio/Band/4G\",\"%s\"", bandstr4G_enc); + ctx->cmds[0].timeout = ctx->cmds[1].timeout = ctx->cmds[2].timeout = 60; + } else { + ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, 3 + 1); + ctx->cmds[0].command = g_strdup_printf ("^SCFG=\"Radio/Band/2G\",\"%08x\",,1", band[MM_CINTERION_RB_BLOCK_GSM]); + ctx->cmds[1].command = g_strdup_printf ("^SCFG=\"Radio/Band/3G\",\"%08x\",,1", band[MM_CINTERION_RB_BLOCK_UMTS]); + ctx->cmds[2].command = g_strdup_printf ("^SCFG=\"Radio/Band/4G\",\"%08x\",\"%08x\",1", band[MM_CINTERION_RB_BLOCK_LTE_LOW], band[MM_CINTERION_RB_BLOCK_LTE_HIGH]); + ctx->cmds[0].timeout = ctx->cmds[1].timeout = ctx->cmds[2].timeout = 15; + } + + mm_base_modem_at_sequence (MM_BASE_MODEM (self), + (const MMBaseModemAtCommand *)ctx->cmds, + NULL, + NULL, + (GAsyncReadyCallback)scfg_set_ready_sequence, + task); + return; + } + + g_assert_not_reached (); +} + +static void +set_bands_2g (GTask *task, + GArray *bands_array) +{ + MMBroadbandModemCinterion *self; + GError *error = NULL; + guint band[MM_CINTERION_RB_BLOCK_N] = { 0 }; + g_autofree gchar *cmd = NULL; + g_autofree gchar *bandstr = NULL; + g_autofree gchar *bandstr_enc = NULL; + + self = g_task_get_source_object (task); + + if (!mm_cinterion_build_band (bands_array, + self->priv->supported_bands, + TRUE, /* 2G only */ + MM_CINTERION_RADIO_BAND_FORMAT_SINGLE, + 0, + band, + &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Build string with the value, in the proper charset */ + bandstr = g_strdup_printf ("%u", band[MM_CINTERION_RB_BLOCK_LEGACY]); + bandstr_enc = mm_modem_charset_str_from_utf8 (bandstr, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + FALSE, + &error); + if (!bandstr_enc) { + g_prefix_error (&error, "Couldn't convert band string to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Following the setup: + * AT^SCFG="Radion/Band",<rbp>,<rba> + * We will set the preferred band equal to the allowed band, so that we force + * the modem to connect at that specific frequency only. Note that we will be + * passing double-quote enclosed strings here! + */ + cmd = g_strdup_printf ("^SCFG=\"Radio/Band\",\"%s\",\"%s\"", bandstr_enc, bandstr_enc); + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 15, + FALSE, + (GAsyncReadyCallback)scfg_set_ready, + task); +} + +static void +set_current_bands (MMIfaceModem *self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + /* The bands that we get here are previously validated by the interface, and + * that means that ALL the bands given here were also given in the list of + * supported bands. BUT BUT, that doesn't mean that the exact list of bands + * will end up being valid, as not all combinations are possible. E.g, + * Cinterion modems supporting only 2G have specific combinations allowed. + */ + task = g_task_new (self, NULL, callback, user_data); + if (mm_iface_modem_is_3g (self)) + set_bands_3g (task, bands_array); + else + set_bands_2g (task, bands_array); +} + +/*****************************************************************************/ +/* Flow control */ + +static gboolean +setup_flow_control_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +setup_flow_control_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + /* Let the error be critical. We DO need RTS/CTS in order to have + * proper modem disabling. */ + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +setup_flow_control (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* We need to enable RTS/CTS so that CYCLIC SLEEP mode works */ + g_object_set (self, MM_BROADBAND_MODEM_FLOW_CONTROL, MM_FLOW_CONTROL_RTS_CTS, NULL); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "\\Q3", + 3, + FALSE, + (GAsyncReadyCallback)setup_flow_control_ready, + task); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +typedef struct { + MMUnlockRetries *retries; + guint i; +} LoadUnlockRetriesContext; + +typedef struct { + MMModemLock lock; + const gchar *command; +} UnlockRetriesMap; + +static const UnlockRetriesMap unlock_retries_map [] = { + { MM_MODEM_LOCK_SIM_PIN, "^SPIC=\"SC\"" }, + { MM_MODEM_LOCK_SIM_PUK, "^SPIC=\"SC\",1" }, + { MM_MODEM_LOCK_SIM_PIN2, "^SPIC=\"P2\"" }, + { MM_MODEM_LOCK_SIM_PUK2, "^SPIC=\"P2\",1" }, + { MM_MODEM_LOCK_PH_FSIM_PIN, "^SPIC=\"PS\"" }, + { MM_MODEM_LOCK_PH_FSIM_PUK, "^SPIC=\"PS\",1" }, + { MM_MODEM_LOCK_PH_NET_PIN, "^SPIC=\"PN\"" }, + { MM_MODEM_LOCK_PH_NET_PUK, "^SPIC=\"PN\",1" }, +}; + +static void +load_unlock_retries_context_free (LoadUnlockRetriesContext *ctx) +{ + g_object_unref (ctx->retries); + g_slice_free (LoadUnlockRetriesContext, ctx); +} + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void load_unlock_retries_context_step (GTask *task); + +static void +spic_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LoadUnlockRetriesContext *ctx; + const gchar *response; + g_autoptr(GError) error = NULL; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + mm_obj_dbg (self, "Couldn't load retry count for lock '%s': %s", + mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock), + error->message); + } else { + guint val; + + response = mm_strip_tag (response, "^SPIC:"); + if (!mm_get_uint_from_str (response, &val)) + mm_obj_dbg (self, "couldn't parse retry count value for lock '%s'", + mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock)); + else + mm_unlock_retries_set (ctx->retries, unlock_retries_map[ctx->i].lock, val); + } + + /* Go to next lock value */ + ctx->i++; + load_unlock_retries_context_step (task); +} + +static void +load_unlock_retries_context_step (GTask *task) +{ + MMBroadbandModemCinterion *self; + LoadUnlockRetriesContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + if (ctx->i == G_N_ELEMENTS (unlock_retries_map)) { + g_task_return_pointer (task, g_object_ref (ctx->retries), g_object_unref); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + unlock_retries_map[ctx->i].command, + 3, + FALSE, + (GAsyncReadyCallback)spic_ready, + task); +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LoadUnlockRetriesContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_slice_new0 (LoadUnlockRetriesContext); + ctx->retries = mm_unlock_retries_new (); + ctx->i = 0; + g_task_set_task_data (task, ctx, (GDestroyNotify)load_unlock_retries_context_free); + + load_unlock_retries_context_step (task); +} + +/*****************************************************************************/ +/* After SIM unlock (Modem interface) */ + +#define MAX_AFTER_SIM_UNLOCK_RETRIES 15 + +typedef enum { + CINTERION_SIM_STATUS_REMOVED = 0, + CINTERION_SIM_STATUS_INSERTED = 1, + CINTERION_SIM_STATUS_INIT_COMPLETED = 5, +} CinterionSimStatus; + +typedef struct { + guint retries; + guint timeout_id; +} AfterSimUnlockContext; + +static gboolean +after_sim_unlock_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void after_sim_unlock_context_step (GTask *task); + +static gboolean +simstatus_timeout_cb (GTask *task) +{ + AfterSimUnlockContext *ctx; + + ctx = g_task_get_task_data (task); + ctx->timeout_id = 0; + after_sim_unlock_context_step (task); + return G_SOURCE_REMOVE; +} + +static void +simstatus_check_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + AfterSimUnlockContext *ctx; + const gchar *response; + + response = mm_base_modem_at_command_finish (self, res, NULL); + if (response) { + gchar *descr = NULL; + guint val = 0; + + if (mm_cinterion_parse_sind_response (response, &descr, NULL, &val, NULL) && + g_str_equal (descr, "simstatus") && + val == CINTERION_SIM_STATUS_INIT_COMPLETED) { + /* SIM ready! */ + g_free (descr); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + g_free (descr); + } + + /* Need to retry after 1 sec */ + ctx = g_task_get_task_data (task); + g_assert (ctx->timeout_id == 0); + ctx->timeout_id = g_timeout_add_seconds (1, (GSourceFunc)simstatus_timeout_cb, task); +} + +static void +after_sim_unlock_context_step (GTask *task) +{ + MMBroadbandModemCinterion *self; + AfterSimUnlockContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* if not supported or too much wait, skip */ + if (self->priv->sind_simstatus_support != FEATURE_SUPPORTED || ctx->retries == 0) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Recheck */ + ctx->retries--; + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SIND=\"simstatus\",2", + 3, + FALSE, + (GAsyncReadyCallback)simstatus_check_ready, + task); +} + +static void +sind_indicators_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self; + g_autoptr(GError) error = NULL; + const gchar *response; + + self = MM_BROADBAND_MODEM_CINTERION (_self); + if (!(response = mm_base_modem_at_command_finish (_self, res, &error))) { + self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED; + mm_obj_dbg (self, "psinfo support? no"); + + self->priv->sind_simstatus_support = FEATURE_NOT_SUPPORTED; + mm_obj_dbg (self, "simstatus support? no"); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + + return; + } + + if (g_regex_match_simple ("\\(\\s*psinfo\\s*,", response, 0, 0)) + self->priv->sind_psinfo_support = FEATURE_SUPPORTED; + mm_obj_dbg (self, "psinfo support? %s", self->priv->sind_psinfo_support == FEATURE_SUPPORTED ? "yes":"no"); + + if (g_regex_match_simple ("\\(\\s*simstatus\\s*,", response, 0, 0)) + self->priv->sind_simstatus_support = FEATURE_SUPPORTED; + mm_obj_dbg (self, "simstatus support? %s", self->priv->sind_simstatus_support == FEATURE_SUPPORTED ? "yes":"no"); + + after_sim_unlock_context_step (task); +} + +static void +after_sim_unlock (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + AfterSimUnlockContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + ctx = g_new0 (AfterSimUnlockContext, 1); + ctx->retries = MAX_AFTER_SIM_UNLOCK_RETRIES; + g_task_set_task_data (task, ctx, g_free); + + /* check which indicators are available */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SIND=?", + 3, + FALSE, + (GAsyncReadyCallback)sind_indicators_ready, + task); +} + +/*****************************************************************************/ +/* Setup SIM hot swap (Modem interface) */ + +static void +cinterion_scks_unsolicited_handler (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemCinterion *self) +{ + guint scks; + + if (!mm_get_uint_from_match_info (match_info, 1, &scks)) + return; + + switch (scks) { + case 0: + mm_obj_msg (self, "SIM removal detected"); + break; + case 1: + mm_obj_msg (self, "SIM insertion detected"); + break; + case 2: + mm_obj_msg (self, "SIM interface hardware deactivated (potentially non-electrically compatible SIM inserted)"); + break; + case 3: + mm_obj_msg (self, "SIM interface hardware deactivated (technical problem, no precise diagnosis)"); + break; + default: + g_assert_not_reached (); + break; + } + + mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self)); +} + +static gboolean +modem_setup_sim_hot_swap_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cinterion_hot_swap_init_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + g_autoptr(GError) error = NULL; + MMPortSerialAt *primary; + MMPortSerialAt *secondary; + + if (!mm_base_modem_at_command_finish (_self, res, &error)) { + g_prefix_error (&error, "Could not enable SCKS: "); + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "SIM hot swap detect successfully enabled"); + + primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + mm_port_serial_at_add_unsolicited_msg_handler ( + primary, + self->priv->scks_regex, + (MMPortSerialAtUnsolicitedMsgFn) cinterion_scks_unsolicited_handler, + self, + NULL); + + secondary = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + if (secondary) + mm_port_serial_at_add_unsolicited_msg_handler ( + secondary, + self->priv->scks_regex, + (MMPortSerialAtUnsolicitedMsgFn) cinterion_scks_unsolicited_handler, + self, + NULL); + + if (!mm_broadband_modem_sim_hot_swap_ports_context_init (MM_BROADBAND_MODEM (self), &error)) + mm_obj_warn (self, "failed to initialize SIM hot swap ports context: %s", error->message); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_setup_sim_hot_swap (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + mm_obj_dbg (self, "Enabling SCKS URCs for SIM hot swap detection"); + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SCKS=1", + 3, + FALSE, + (GAsyncReadyCallback) cinterion_hot_swap_init_ready, + task); +} + +/*****************************************************************************/ +/* SIM hot swap cleanup (Modem interface) */ + +static void +modem_cleanup_sim_hot_swap (MMIfaceModem *self) +{ + mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self)); +} + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +cinterion_modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_cinterion_new_ready (GObject *unused, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer; + GError *error = NULL; + + bearer = mm_broadband_bearer_cinterion_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + g_object_unref (task); +} + +static void +broadband_bearer_new_ready (GObject *unused, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer; + GError *error = NULL; + + bearer = mm_broadband_bearer_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + g_object_unref (task); +} + +static void +common_create_bearer (GTask *task) +{ + MMBroadbandModemCinterion *self; + + self = g_task_get_source_object (task); + + switch (self->priv->swwan_support) { + case FEATURE_NOT_SUPPORTED: + mm_obj_dbg (self, "^SWWAN not supported, creating default bearer..."); + mm_broadband_bearer_new (MM_BROADBAND_MODEM (self), + g_task_get_task_data (task), + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_new_ready, + task); + return; + case FEATURE_SUPPORTED: + mm_obj_dbg (self, "^SWWAN supported, creating cinterion bearer..."); + mm_broadband_bearer_cinterion_new (MM_BROADBAND_MODEM_CINTERION (self), + g_task_get_task_data (task), + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_cinterion_new_ready, + task); + return; + case FEATURE_SUPPORT_UNKNOWN: + default: + g_assert_not_reached (); + } +} + +static void +swwan_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + + /* Fetch the result to the SWWAN test. If no response given (error triggered), + * assume unsupported */ + if (!mm_base_modem_at_command_finish (_self, res, NULL)) { + mm_obj_dbg (self, "SWWAN unsupported"); + self->priv->swwan_support = FEATURE_NOT_SUPPORTED; + } else { + mm_obj_dbg (self, "SWWAN supported"); + self->priv->swwan_support = FEATURE_SUPPORTED; + } + + /* Go on and create the bearer */ + common_create_bearer (task); +} + +static void +cinterion_modem_create_bearer (MMIfaceModem *_self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, g_object_ref (properties), g_object_unref); + + /* Newer Cinterion modems may support SWWAN, which is the same as WWAN. + * Check to see if current modem supports it.*/ + if (self->priv->swwan_support != FEATURE_SUPPORT_UNKNOWN) { + common_create_bearer (task); + return; + } + + /* If we don't have a data port, don't even bother checking for ^SWWAN + * support. */ + if (!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) { + mm_obj_dbg (self, "skipping ^SWWAN check as no data port is available"); + self->priv->swwan_support = FEATURE_NOT_SUPPORTED; + common_create_bearer (task); + return; + } + + mm_obj_dbg (self, "checking ^SWWAN support..."); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SWWAN=?", + 6, + TRUE, /* may be cached */ + (GAsyncReadyCallback) swwan_test_ready, + task); +} + +/*****************************************************************************/ + +static void +setup_ports (MMBroadbandModem *_self) +{ + MMBroadbandModemCinterion *self = (MM_BROADBAND_MODEM_CINTERION (_self)); + MMPortSerialAt *ports[2]; + guint i; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_cinterion_parent_class)->setup_ports (_self); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->sysstart_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->scks_regex, + NULL, NULL, NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemCinterion * +mm_broadband_modem_cinterion_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_CINTERION, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer (TTY) or Cinterion bearer (NET) supported */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_cinterion_init (MMBroadbandModemCinterion *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_MODEM_CINTERION, + MMBroadbandModemCinterionPrivate); + + /* Initialize private variables */ + self->priv->initial_eps_bearer_cid = -1; + self->priv->sind_psinfo_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->swwan_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->smoni_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->sind_simstatus_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->sxrat_support = FEATURE_SUPPORT_UNKNOWN; + + self->priv->ciev_regex = g_regex_new ("\\r\\n\\+CIEV:\\s*([a-z]+),(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->sysstart_regex = g_regex_new ("\\r\\n\\^SYSSTART.*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->scks_regex = g_regex_new ("\\^SCKS:\\s*([0-3])\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + self->priv->any_allowed = MM_MODEM_MODE_NONE; +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (object); + + g_free (self->priv->sleep_mode_cmd); + + if (self->priv->cnmi_supported_mode) + g_array_unref (self->priv->cnmi_supported_mode); + if (self->priv->cnmi_supported_mt) + g_array_unref (self->priv->cnmi_supported_mt); + if (self->priv->cnmi_supported_bm) + g_array_unref (self->priv->cnmi_supported_bm); + if (self->priv->cnmi_supported_ds) + g_array_unref (self->priv->cnmi_supported_ds); + if (self->priv->cnmi_supported_bfr) + g_array_unref (self->priv->cnmi_supported_bfr); + if (self->priv->sxrat_supported_rat) + g_array_unref (self->priv->sxrat_supported_rat); + if (self->priv->sxrat_supported_pref1) + g_array_unref (self->priv->sxrat_supported_pref1); + + g_regex_unref (self->priv->ciev_regex); + g_regex_unref (self->priv->sysstart_regex); + g_regex_unref (self->priv->scks_regex); + + G_OBJECT_CLASS (mm_broadband_modem_cinterion_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->create_bearer = cinterion_modem_create_bearer; + iface->create_bearer_finish = cinterion_modem_create_bearer_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->load_supported_bands = load_supported_bands; + iface->load_supported_bands_finish = load_supported_bands_finish; + iface->load_current_bands = load_current_bands; + iface->load_current_bands_finish = load_current_bands_finish; + iface->set_current_bands = set_current_bands; + iface->set_current_bands_finish = set_current_bands_finish; + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->setup_flow_control = setup_flow_control; + iface->setup_flow_control_finish = setup_flow_control_finish; + iface->modem_after_sim_unlock = after_sim_unlock; + iface->modem_after_sim_unlock_finish = after_sim_unlock_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; + iface->reset = mm_shared_cinterion_modem_reset; + iface->reset_finish = mm_shared_cinterion_modem_reset_finish; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; + iface->modem_power_off = modem_power_off; + iface->modem_power_off_finish = modem_power_off_finish; + iface->setup_sim_hot_swap = modem_setup_sim_hot_swap; + iface->setup_sim_hot_swap_finish = modem_setup_sim_hot_swap_finish; + iface->cleanup_sim_hot_swap = modem_cleanup_sim_hot_swap; +} + +static MMIfaceModem * +peek_parent_interface (MMSharedCinterion *self) +{ + return iface_modem_parent; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish; + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + + iface->load_initial_eps_bearer = modem_3gpp_load_initial_eps_bearer; + iface->load_initial_eps_bearer_finish = modem_3gpp_load_initial_eps_bearer_finish; + iface->load_initial_eps_bearer_settings = modem_3gpp_load_initial_eps_bearer_settings; + iface->load_initial_eps_bearer_settings_finish = modem_3gpp_load_initial_eps_bearer_settings_finish; + iface->set_initial_eps_bearer_settings = modem_3gpp_set_initial_eps_bearer_settings; + iface->set_initial_eps_bearer_settings_finish = modem_3gpp_set_initial_eps_bearer_settings_finish; + +} + +static void +iface_modem_messaging_init (MMIfaceModemMessaging *iface) +{ + iface->check_support = messaging_check_support; + iface->check_support_finish = messaging_check_support_finish; + iface->enable_unsolicited_events = messaging_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = messaging_enable_unsolicited_events_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_cinterion_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_cinterion_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_cinterion_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_cinterion_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_cinterion_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_cinterion_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedCinterion *self) +{ + return iface_modem_location_parent; +} + +static void +iface_modem_voice_init (MMIfaceModemVoice *iface) +{ + iface_modem_voice_parent = g_type_interface_peek_parent (iface); + + iface->create_call = mm_shared_cinterion_create_call; + + iface->check_support = mm_shared_cinterion_voice_check_support; + iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish; + iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish; + iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish; +} + +static MMIfaceModemVoice * +peek_parent_voice_interface (MMSharedCinterion *self) +{ + return iface_modem_voice_parent; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface_modem_time_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish; +} + +static MMIfaceModemTime * +peek_parent_time_interface (MMSharedCinterion *self) +{ + return iface_modem_time_parent; +} + +static void +shared_cinterion_init (MMSharedCinterion *iface) +{ + iface->peek_parent_interface = peek_parent_interface; + iface->peek_parent_location_interface = peek_parent_location_interface; + iface->peek_parent_voice_interface = peek_parent_voice_interface; + iface->peek_parent_time_interface = peek_parent_time_interface; +} + +static void +iface_modem_signal_init (MMIfaceModemSignal *iface) +{ + iface_modem_signal_parent = g_type_interface_peek_parent (iface); + + iface->check_support = signal_check_support; + iface->check_support_finish = signal_check_support_finish; + iface->load_values = signal_load_values; + iface->load_values_finish = signal_load_values_finish; +} + +static void +mm_broadband_modem_cinterion_class_init (MMBroadbandModemCinterionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemCinterionPrivate)); + + /* Virtual methods */ + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/cinterion/mm-broadband-modem-cinterion.h b/src/plugins/cinterion/mm-broadband-modem-cinterion.h new file mode 100644 index 00000000..555ee084 --- /dev/null +++ b/src/plugins/cinterion/mm-broadband-modem-cinterion.h @@ -0,0 +1,54 @@ +/* -*- 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 Ammonit Measurement GmbH + * Copyright (C) 2011 Google Inc. + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#ifndef MM_BROADBAND_MODEM_CINTERION_H +#define MM_BROADBAND_MODEM_CINTERION_H + +#include "mm-broadband-modem.h" +#include "mm-modem-helpers-cinterion.h" + +#define MM_TYPE_BROADBAND_MODEM_CINTERION (mm_broadband_modem_cinterion_get_type ()) +#define MM_BROADBAND_MODEM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_CINTERION, MMBroadbandModemCinterion)) +#define MM_BROADBAND_MODEM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_CINTERION, MMBroadbandModemCinterionClass)) +#define MM_IS_BROADBAND_MODEM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_CINTERION)) +#define MM_IS_BROADBAND_MODEM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_CINTERION)) +#define MM_BROADBAND_MODEM_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_CINTERION, MMBroadbandModemCinterionClass)) + +typedef struct _MMBroadbandModemCinterion MMBroadbandModemCinterion; +typedef struct _MMBroadbandModemCinterionClass MMBroadbandModemCinterionClass; +typedef struct _MMBroadbandModemCinterionPrivate MMBroadbandModemCinterionPrivate; + +struct _MMBroadbandModemCinterion { + MMBroadbandModem parent; + MMBroadbandModemCinterionPrivate *priv; +}; + +struct _MMBroadbandModemCinterionClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_cinterion_get_type (void); + +MMBroadbandModemCinterion *mm_broadband_modem_cinterion_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +MMCinterionModemFamily mm_broadband_modem_cinterion_get_family (MMBroadbandModemCinterion * modem); + +#endif /* MM_BROADBAND_MODEM_CINTERION_H */ diff --git a/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.c b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.c new file mode 100644 index 00000000..740909b1 --- /dev/null +++ b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.c @@ -0,0 +1,167 @@ +/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" +#include "mm-broadband-modem-mbim-cinterion.h" +#include "mm-shared-cinterion.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_voice_init (MMIfaceModemVoice *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); +static void shared_cinterion_init (MMSharedCinterion *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModemLocation *iface_modem_location_parent; +static MMIfaceModemVoice *iface_modem_voice_parent; +static MMIfaceModemTime *iface_modem_time_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimCinterion, mm_broadband_modem_mbim_cinterion, MM_TYPE_BROADBAND_MODEM_MBIM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init)) + +/*****************************************************************************/ + +MMBroadbandModemMbimCinterion * +mm_broadband_modem_mbim_cinterion_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* MBIM bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + MM_BROADBAND_MODEM_MBIM_INTEL_FIRMWARE_UPDATE_UNSUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_mbim_cinterion_init (MMBroadbandModemMbimCinterion *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->reset = mm_shared_cinterion_modem_reset; + iface->reset_finish = mm_shared_cinterion_modem_reset_finish; +} + +static MMIfaceModem * +peek_parent_interface (MMSharedCinterion *self) +{ + return iface_modem_parent; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_cinterion_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_cinterion_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_cinterion_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_cinterion_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_cinterion_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_cinterion_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedCinterion *self) +{ + return iface_modem_location_parent; +} + +static void +iface_modem_voice_init (MMIfaceModemVoice *iface) +{ + iface_modem_voice_parent = g_type_interface_peek_parent (iface); + + iface->create_call = mm_shared_cinterion_create_call; + + iface->check_support = mm_shared_cinterion_voice_check_support; + iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish; + iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish; + iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish; +} + +static MMIfaceModemVoice * +peek_parent_voice_interface (MMSharedCinterion *self) +{ + return iface_modem_voice_parent; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface_modem_time_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish; +} + +static MMIfaceModemTime * +peek_parent_time_interface (MMSharedCinterion *self) +{ + return iface_modem_time_parent; +} + +static void +shared_cinterion_init (MMSharedCinterion *iface) +{ + iface->peek_parent_interface = peek_parent_interface; + iface->peek_parent_location_interface = peek_parent_location_interface; + iface->peek_parent_voice_interface = peek_parent_voice_interface; + iface->peek_parent_time_interface = peek_parent_time_interface; +} + +static void +mm_broadband_modem_mbim_cinterion_class_init (MMBroadbandModemMbimCinterionClass *klass) +{ +} diff --git a/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.h b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.h new file mode 100644 index 00000000..a2f2ef68 --- /dev/null +++ b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.h @@ -0,0 +1,47 @@ +/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_MBIM_CINTERION_MBIM_H +#define MM_BROADBAND_MODEM_MBIM_CINTERION_MBIM_H + +#include "mm-broadband-modem-mbim.h" + +#define MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION (mm_broadband_modem_mbim_cinterion_get_type ()) +#define MM_BROADBAND_MODEM_MBIM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION, MMBroadbandModemMbimCinterion)) +#define MM_BROADBAND_MODEM_MBIM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION, MMBroadbandModemMbimCinterionClass)) +#define MM_IS_BROADBAND_MODEM_MBIM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION)) +#define MM_IS_BROADBAND_MODEM_MBIM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION)) +#define MM_BROADBAND_MODEM_MBIM_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION, MMBroadbandModemMbimCinterionClass)) + +typedef struct _MMBroadbandModemMbimCinterion MMBroadbandModemMbimCinterion; +typedef struct _MMBroadbandModemMbimCinterionClass MMBroadbandModemMbimCinterionClass; + +struct _MMBroadbandModemMbimCinterion { + MMBroadbandModemMbim parent; +}; + +struct _MMBroadbandModemMbimCinterionClass{ + MMBroadbandModemMbimClass parent; +}; + +GType mm_broadband_modem_mbim_cinterion_get_type (void); + +MMBroadbandModemMbimCinterion *mm_broadband_modem_mbim_cinterion_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_MBIM_CINTERION_H */ diff --git a/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c new file mode 100644 index 00000000..b94e63d3 --- /dev/null +++ b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c @@ -0,0 +1,167 @@ +/* -*- 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) 2014 Ammonit Measurement GmbH + * Author: Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" +#include "mm-broadband-modem-qmi-cinterion.h" +#include "mm-shared-cinterion.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_voice_init (MMIfaceModemVoice *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); +static void shared_cinterion_init (MMSharedCinterion *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModemLocation *iface_modem_location_parent; +static MMIfaceModemVoice *iface_modem_voice_parent; +static MMIfaceModemTime *iface_modem_time_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiCinterion, mm_broadband_modem_qmi_cinterion, MM_TYPE_BROADBAND_MODEM_QMI, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init)) + +/*****************************************************************************/ + +MMBroadbandModemQmiCinterion * +mm_broadband_modem_qmi_cinterion_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_QMI_CINTERION, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* QMI bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_qmi_cinterion_init (MMBroadbandModemQmiCinterion *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->reset = mm_shared_cinterion_modem_reset; + iface->reset_finish = mm_shared_cinterion_modem_reset_finish; +} + +static MMIfaceModem * +peek_parent_interface (MMSharedCinterion *self) +{ + return iface_modem_parent; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_cinterion_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_cinterion_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_cinterion_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_cinterion_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_cinterion_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_cinterion_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedCinterion *self) +{ + return iface_modem_location_parent; +} + +static void +iface_modem_voice_init (MMIfaceModemVoice *iface) +{ + iface_modem_voice_parent = g_type_interface_peek_parent (iface); + + iface->create_call = mm_shared_cinterion_create_call; + + iface->check_support = mm_shared_cinterion_voice_check_support; + iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish; + iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish; + iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish; +} + +static MMIfaceModemVoice * +peek_parent_voice_interface (MMSharedCinterion *self) +{ + return iface_modem_voice_parent; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface_modem_time_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish; +} + +static MMIfaceModemTime * +peek_parent_time_interface (MMSharedCinterion *self) +{ + return iface_modem_time_parent; +} + +static void +shared_cinterion_init (MMSharedCinterion *iface) +{ + iface->peek_parent_interface = peek_parent_interface; + iface->peek_parent_location_interface = peek_parent_location_interface; + iface->peek_parent_voice_interface = peek_parent_voice_interface; + iface->peek_parent_time_interface = peek_parent_time_interface; +} + +static void +mm_broadband_modem_qmi_cinterion_class_init (MMBroadbandModemQmiCinterionClass *klass) +{ +} diff --git a/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.h b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.h new file mode 100644 index 00000000..ac8f68be --- /dev/null +++ b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.h @@ -0,0 +1,48 @@ +/* -*- 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) 2014 Ammonit Measurement GmbH + * Author: Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_QMI_CINTERION_QMI_H +#define MM_BROADBAND_MODEM_QMI_CINTERION_QMI_H + +#include "mm-broadband-modem-qmi.h" + +#define MM_TYPE_BROADBAND_MODEM_QMI_CINTERION (mm_broadband_modem_qmi_cinterion_get_type ()) +#define MM_BROADBAND_MODEM_QMI_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION, MMBroadbandModemQmiCinterion)) +#define MM_BROADBAND_MODEM_QMI_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION, MMBroadbandModemQmiCinterionClass)) +#define MM_IS_BROADBAND_MODEM_QMI_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION)) +#define MM_IS_BROADBAND_MODEM_QMI_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION)) +#define MM_BROADBAND_MODEM_QMI_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION, MMBroadbandModemQmiCinterionClass)) + +typedef struct _MMBroadbandModemQmiCinterion MMBroadbandModemQmiCinterion; +typedef struct _MMBroadbandModemQmiCinterionClass MMBroadbandModemQmiCinterionClass; + +struct _MMBroadbandModemQmiCinterion { + MMBroadbandModemQmi parent; +}; + +struct _MMBroadbandModemQmiCinterionClass{ + MMBroadbandModemQmiClass parent; +}; + +GType mm_broadband_modem_qmi_cinterion_get_type (void); + +MMBroadbandModemQmiCinterion *mm_broadband_modem_qmi_cinterion_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_QMI_CINTERION_H */ diff --git a/src/plugins/cinterion/mm-modem-helpers-cinterion.c b/src/plugins/cinterion/mm-modem-helpers-cinterion.c new file mode 100644 index 00000000..f22a998c --- /dev/null +++ b/src/plugins/cinterion/mm-modem-helpers-cinterion.c @@ -0,0 +1,1804 @@ +/* -*- 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) 2014 Aleksander Morgado <aleksander@aleksander.es> + * Copyright (C) 2016 Trimble Navigation Limited + * Copyright (C) 2016 Matthew Stanger <matthew_stanger@trimble.com> + * Copyright (C) 2019 Purism SPC + */ + +#include <config.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "ModemManager.h" +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> +#include "mm-log-object.h" +#include "mm-charsets.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers-cinterion.h" +#include "mm-modem-helpers.h" +#include "mm-common-helpers.h" +#include "mm-port-serial-at.h" + +/* Setup relationship between the 3G band bitmask in the modem and the bitmask + * in ModemManager. */ +typedef struct { + guint32 cinterion_band_flag; + MMModemBand mm_band; +} CinterionBand; + +typedef struct { + MMCinterionRbBlock cinterion_band_block; + guint32 cinterion_band_flag; + MMModemBand mm_band; +} CinterionBandEx; + +/* Table checked in PLS8-X/E/J/V/US, HC25 & PHS8 references. The table includes 2/3/4G + * frequencies. Depending on which one is configured, one access technology or + * the other will be used. This may conflict with the allowed mode configuration + * set, so you shouldn't for example set 3G frequency bands, and then use a + * 2G-only allowed mode. */ +static const CinterionBand cinterion_bands[] = { + { (1 << 0), MM_MODEM_BAND_EGSM }, + { (1 << 1), MM_MODEM_BAND_DCS }, + { (1 << 2), MM_MODEM_BAND_G850 }, + { (1 << 3), MM_MODEM_BAND_PCS }, + { (1 << 4), MM_MODEM_BAND_UTRAN_1 }, + { (1 << 5), MM_MODEM_BAND_UTRAN_2 }, + { (1 << 6), MM_MODEM_BAND_UTRAN_5 }, + { (1 << 7), MM_MODEM_BAND_UTRAN_8 }, + { (1 << 8), MM_MODEM_BAND_UTRAN_6 }, + { (1 << 9), MM_MODEM_BAND_UTRAN_4 }, + { (1 << 10), MM_MODEM_BAND_UTRAN_19 }, + { (1 << 12), MM_MODEM_BAND_UTRAN_3 }, + { (1 << 13), MM_MODEM_BAND_EUTRAN_1 }, + { (1 << 14), MM_MODEM_BAND_EUTRAN_2 }, + { (1 << 15), MM_MODEM_BAND_EUTRAN_3 }, + { (1 << 16), MM_MODEM_BAND_EUTRAN_4 }, + { (1 << 17), MM_MODEM_BAND_EUTRAN_5 }, + { (1 << 18), MM_MODEM_BAND_EUTRAN_7 }, + { (1 << 19), MM_MODEM_BAND_EUTRAN_8 }, + { (1 << 20), MM_MODEM_BAND_EUTRAN_17 }, + { (1 << 21), MM_MODEM_BAND_EUTRAN_20 }, + { (1 << 22), MM_MODEM_BAND_EUTRAN_13 }, + { (1 << 24), MM_MODEM_BAND_EUTRAN_19 } +}; + +static const CinterionBandEx cinterion_bands_ex[] = { + { MM_CINTERION_RB_BLOCK_GSM, 0x00000001, MM_MODEM_BAND_EGSM }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000002, MM_MODEM_BAND_DCS }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000004, MM_MODEM_BAND_G850 }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000008, MM_MODEM_BAND_PCS }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000001, MM_MODEM_BAND_UTRAN_1 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000002, MM_MODEM_BAND_UTRAN_2 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000004, MM_MODEM_BAND_UTRAN_3 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000008, MM_MODEM_BAND_UTRAN_4 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000010, MM_MODEM_BAND_UTRAN_5 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000020, MM_MODEM_BAND_UTRAN_6 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000080, MM_MODEM_BAND_UTRAN_8 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000100, MM_MODEM_BAND_UTRAN_9 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00040000, MM_MODEM_BAND_UTRAN_19 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000001, MM_MODEM_BAND_EUTRAN_1 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000002, MM_MODEM_BAND_EUTRAN_2 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000004, MM_MODEM_BAND_EUTRAN_3 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000008, MM_MODEM_BAND_EUTRAN_4 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000010, MM_MODEM_BAND_EUTRAN_5 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000040, MM_MODEM_BAND_EUTRAN_7 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000080, MM_MODEM_BAND_EUTRAN_8 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000800, MM_MODEM_BAND_EUTRAN_12 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00001000, MM_MODEM_BAND_EUTRAN_13 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00010000, MM_MODEM_BAND_EUTRAN_17 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00020000, MM_MODEM_BAND_EUTRAN_18 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00040000, MM_MODEM_BAND_EUTRAN_19 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00080000, MM_MODEM_BAND_EUTRAN_20 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x02000000, MM_MODEM_BAND_EUTRAN_26 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x08000000, MM_MODEM_BAND_EUTRAN_28 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x10000000, MM_MODEM_BAND_EUTRAN_29 }, + { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000020, MM_MODEM_BAND_EUTRAN_38 }, + { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000040, MM_MODEM_BAND_EUTRAN_39 }, + { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000080, MM_MODEM_BAND_EUTRAN_40 }, + { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000100, MM_MODEM_BAND_EUTRAN_41 } +}; + +static const CinterionBandEx cinterion_bands_imt[] = { + { MM_CINTERION_RB_BLOCK_GSM, 0x00000004, MM_MODEM_BAND_EGSM }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000010, MM_MODEM_BAND_DCS }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000020, MM_MODEM_BAND_PCS }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000040, MM_MODEM_BAND_G850 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000001, MM_MODEM_BAND_UTRAN_1 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000002, MM_MODEM_BAND_UTRAN_2 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000008, MM_MODEM_BAND_UTRAN_4 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000010, MM_MODEM_BAND_UTRAN_5 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000080, MM_MODEM_BAND_UTRAN_8 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000100, MM_MODEM_BAND_UTRAN_9 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00040000, MM_MODEM_BAND_UTRAN_19 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000001, MM_MODEM_BAND_EUTRAN_1 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000002, MM_MODEM_BAND_EUTRAN_2 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000004, MM_MODEM_BAND_EUTRAN_3 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000008, MM_MODEM_BAND_EUTRAN_4 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000010, MM_MODEM_BAND_EUTRAN_5 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000040, MM_MODEM_BAND_EUTRAN_7 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000080, MM_MODEM_BAND_EUTRAN_8 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000800, MM_MODEM_BAND_EUTRAN_12 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00020000, MM_MODEM_BAND_EUTRAN_18 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00040000, MM_MODEM_BAND_EUTRAN_19 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00080000, MM_MODEM_BAND_EUTRAN_20 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x08000000, MM_MODEM_BAND_EUTRAN_28 } +}; + +/* Check valid combinations in 2G-only devices */ +#define VALIDATE_2G_BAND(cinterion_mask) \ + (cinterion_mask == 1 || \ + cinterion_mask == 2 || \ + cinterion_mask == 4 || \ + cinterion_mask == 8 || \ + cinterion_mask == 3 || \ + cinterion_mask == 5 || \ + cinterion_mask == 10 || \ + cinterion_mask == 12 || \ + cinterion_mask == 15) + +/*****************************************************************************/ +/* ^SCFG (3G+LTE) test parser + * + * Example 3G: + * AT^SCFG=? + * ... + * ^SCFG: "MEShutdown/OnIgnition",("on","off") + * ^SCFG: "Radio/Band",("1-511","0-1") + * ^SCFG: "Radio/NWSM",("0","1","2") + * ... + * ^SCFG: "Radio/Band\",("1"-"147") + * + * Example LTE1 (GSM charset): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G",("0x00000004"-"0x00000074") + * ^SCFG: "Radio/Band/3G",("0x00000001"-"0x0004019B") + * ^SCFG: "Radio/Band/4G",("0x00000001"-"0x080E08DF") + * ... + * + * Example LTE1 (UCS2 charset): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G",("0030007800300030003000300030003000300034"-"0030007800300030003000300030003000370034") + * ^SCFG: "Radio/Band/3G",("0030007800300030003000300030003000300031"-"0030007800300030003000340030003100390042") + * ^SCFG: "Radio/Band/4G",("0030007800300030003000300030003000300031"-"0030007800300038003000450030003800440046") + * ... + * + * Example LTE2 (all charsets): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G",("00000001-0000000f"),,("0","1") + * ^SCFG: "Radio/Band/3G",("00000001-000400b5"),,("0","1") + * ^SCFG: "Radio/Band/4G",("00000001-8a0e00d5"),("00000002-000001e2"),("0","1") + * ... + */ + +static void +parse_bands (guint bandlist, + GArray **bands, + MMCinterionRbBlock block, + MMCinterionModemFamily modem_family) +{ + guint i; + const CinterionBandEx *ref_bands; + guint nb_ref_bands; + + if (!bandlist) + return; + + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) { + ref_bands = cinterion_bands_imt; + nb_ref_bands = G_N_ELEMENTS (cinterion_bands_imt); + } else { + ref_bands = cinterion_bands_ex; + nb_ref_bands = G_N_ELEMENTS (cinterion_bands_ex); + } + + for (i = 0; i < nb_ref_bands; i++) { + if (block == ref_bands[i].cinterion_band_block && (bandlist & ref_bands[i].cinterion_band_flag)) { + if (G_UNLIKELY (!*bands)) + *bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23); + g_array_append_val (*bands, ref_bands[i].mm_band); + } + } +} + +static guint +take_and_convert_from_matched_string (gchar *str, + MMModemCharset charset, + MMCinterionModemFamily modem_family, + GError **error) +{ + guint val = 0; + g_autofree gchar *utf8 = NULL; + g_autofree gchar *taken_str = str; + + if (!taken_str) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, + "Couldn't convert to integer number: no input string"); + return 0; + } + + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) { + utf8 = mm_modem_charset_str_to_utf8 (taken_str, -1, charset, FALSE, error); + if (!utf8) { + g_prefix_error (error, "Couldn't convert to integer number: "); + return 0; + } + } + + if (!mm_get_uint_from_hex_str (utf8 ? utf8 : taken_str, &val)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't convert to integer number: wrong hex encoding: %s", utf8 ? utf8 : taken_str); + return 0; + } + + return val; +} + +gboolean +mm_cinterion_parse_scfg_test (const gchar *response, + MMCinterionModemFamily modem_family, + MMModemCharset charset, + GArray **supported_bands, + MMCinterionRadioBandFormat *format, + GError **error) +{ + g_autoptr(GRegex) r1 = NULL; + g_autoptr(GMatchInfo) match_info1 = NULL; + g_autoptr(GRegex) r2 = NULL; + g_autoptr(GMatchInfo) match_info2 = NULL; + GError *inner_error = NULL; + GArray *bands = NULL; + + g_assert (format); + + if (!response) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response"); + return FALSE; + } + + r1 = g_regex_new ("\\^SCFG:\\s*\"Radio/Band\",\\((?:\")?([0-9]*)(?:\")?-(?:\")?([0-9]*)(?:\")?.*\\)", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r1 != NULL); + + g_regex_match_full (r1, response, strlen (response), 0, 0, &match_info1, &inner_error); + if (inner_error) + goto finish; + if (g_match_info_matches (match_info1)) { + g_autofree gchar *maxbandstr = NULL; + guint maxband = 0; + + *format = MM_CINTERION_RADIO_BAND_FORMAT_SINGLE; + + maxbandstr = mm_get_string_unquoted_from_match_info (match_info1, 2); + if (maxbandstr) + mm_get_uint_from_str (maxbandstr, &maxband); + + if (maxband == 0) { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse ^SCFG=? response"); + } else { + guint i; + + for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) { + if (maxband & cinterion_bands[i].cinterion_band_flag) { + if (G_UNLIKELY (!bands)) + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9); + g_array_append_val (bands, cinterion_bands[i].mm_band); + } + } + } + goto finish; + } + + r2 = g_regex_new ("\\^SCFG:\\s*\"Radio/Band/([234]G)\"," + "\\(\"?([0-9A-Fa-fx]*)\"?-\"?([0-9A-Fa-fx]*)\"?\\)" + "(,*\\(\"?([0-9A-Fa-fx]*)\"?-\"?([0-9A-Fa-fx]*)\"?\\))?", + 0, 0, NULL); + g_assert (r2 != NULL); + + g_regex_match_full (r2, response, strlen (response), 0, 0, &match_info2, &inner_error); + if (inner_error) + goto finish; + + while (g_match_info_matches (match_info2)) { + g_autofree gchar *techstr = NULL; + guint maxband; + + *format = MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE; + + techstr = mm_get_string_unquoted_from_match_info (match_info2, 1); + if (g_strcmp0 (techstr, "2G") == 0) { + maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_GSM, modem_family); + } else if (g_strcmp0 (techstr, "3G") == 0) { + maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_UMTS, modem_family); + } else if (g_strcmp0 (techstr, "4G") == 0) { + maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_LTE_LOW, modem_family); + if (modem_family == MM_CINTERION_MODEM_FAMILY_DEFAULT) { + maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 6), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_LTE_HIGH, modem_family); + } + } else { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse ^SCFG=? response"); + break; + } + + g_match_info_next (match_info2, NULL); + } + +finish: + /* set error only if not already given */ + if (!bands && !inner_error) + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "No valid bands found in ^SCFG=? response"); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + g_assert (bands != NULL && bands->len > 0); + *supported_bands = bands; + + return TRUE; +} + +/*****************************************************************************/ +/* ^SCFG response parser (2 types: 2G/3G and LTE) + * + * Example (3G): + * AT^SCFG="Radio/Band" + * ^SCFG: "Radio/Band",127 + * + * Example (2G, UCS-2): + * AT+SCFG="Radio/Band" + * ^SCFG: "Radio/Band","0031","0031" + * + * Example (2G): + * AT+SCFG="Radio/Band" + * ^SCFG: "Radio/Band","3","3" + * + * Example LTE1 (GSM charset): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G","0x00000074" + * ^SCFG: "Radio/Band/3G","0x0004019B" + * ^SCFG: "Radio/Band/4G","0x080E08DF" + * ... + * AT^SCFG=? + * ... + * Example LTE1 (UCS2 charset): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G","0030007800300030003000300030003000370034" + * ^SCFG: "Radio/Band/3G","0030007800300030003000340030003100390042" + * ^SCFG: "Radio/Band/4G","0030007800300038003000450030003800440046" + * ... + * Example LTE2 (all charsets): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G","0000000f" + * ^SCFG: "Radio/Band/3G","000400b5" + * ^SCFG: "Radio/Band/4G","8a0e00d5","000000e2" + * ... + */ + +gboolean +mm_cinterion_parse_scfg_response (const gchar *response, + MMCinterionModemFamily modem_family, + MMModemCharset charset, + GArray **current_bands, + MMCinterionRadioBandFormat format, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + GArray *bands = NULL; + + if (!response) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response"); + return FALSE; + } + + if (format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) { + r = g_regex_new ("\\^SCFG:\\s*\"Radio/Band\",\\s*\"?([0-9a-fA-F]*)\"?", 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (inner_error) + goto finish; + + if (g_match_info_matches (match_info)) { + g_autofree gchar *currentstr = NULL; + guint current = 0; + + currentstr = mm_get_string_unquoted_from_match_info (match_info, 1); + if (currentstr) + mm_get_uint_from_str (currentstr, ¤t); + + if (current == 0) { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse ^SCFG? response"); + } else { + guint i; + + for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) { + if (current & cinterion_bands[i].cinterion_band_flag) { + if (G_UNLIKELY (!bands)) + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9); + g_array_append_val (bands, cinterion_bands[i].mm_band); + } + } + } + } + } else if (format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE) { + r = g_regex_new ("\\^SCFG:\\s*\"Radio/Band/([234]G)\",\"?([0-9A-Fa-fx]*)\"?,?\"?([0-9A-Fa-fx]*)?\"?", + 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (inner_error) + goto finish; + + while (g_match_info_matches (match_info)) { + g_autofree gchar *techstr = NULL; + guint current; + + techstr = mm_get_string_unquoted_from_match_info (match_info, 1); + if (g_strcmp0 (techstr, "2G") == 0) { + current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_GSM, modem_family); + + } else if (g_strcmp0 (techstr, "3G") == 0) { + current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_UMTS, modem_family); + } else if (g_strcmp0 (techstr, "4G") == 0) { + current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_LTE_LOW, modem_family); + if (modem_family == MM_CINTERION_MODEM_FAMILY_DEFAULT) { + current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 3), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_LTE_HIGH, modem_family); + } + } else { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse ^SCFG? response"); + break; + } + + g_match_info_next (match_info, NULL); + } + } else + g_assert_not_reached (); + +finish: + /* set error only if not already given */ + if (!bands && !inner_error) + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "No valid bands found in ^SCFG response"); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + g_assert (bands != NULL && bands->len > 0); + *current_bands = bands; + + return TRUE; +} + +/*****************************************************************************/ +/* +CNMI test parser + * + * Example (PHS8): + * AT+CNMI=? + * +CNMI: (0,1,2),(0,1),(0,2),(0),(1) + */ + +gboolean +mm_cinterion_parse_cnmi_test (const gchar *response, + GArray **supported_mode, + GArray **supported_mt, + GArray **supported_bm, + GArray **supported_ds, + GArray **supported_bfr, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GArray) tmp_supported_mode = NULL; + g_autoptr(GArray) tmp_supported_mt = NULL; + g_autoptr(GArray) tmp_supported_bm = NULL; + g_autoptr(GArray) tmp_supported_ds = NULL; + g_autoptr(GArray) tmp_supported_bfr = NULL; + GError *inner_error = NULL; + + if (!response) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response"); + return FALSE; + } + + r = g_regex_new ("\\+CNMI:\\s*\\((.*)\\),\\((.*)\\),\\((.*)\\),\\((.*)\\),\\((.*)\\)", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, + 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + if (supported_mode) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 1); + tmp_supported_mode = mm_parse_uint_list (str, &inner_error); + if (inner_error) + goto out; + } + if (supported_mt) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 2); + tmp_supported_mt = mm_parse_uint_list (str, &inner_error); + if (inner_error) + goto out; + } + if (supported_bm) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 3); + tmp_supported_bm = mm_parse_uint_list (str, &inner_error); + if (inner_error) + goto out; + } + if (supported_ds) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 4); + tmp_supported_ds = mm_parse_uint_list (str, &inner_error); + if (inner_error) + goto out; + } + if (supported_bfr) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 5); + tmp_supported_bfr = mm_parse_uint_list (str, &inner_error); + if (inner_error) + goto out; + } + } + +out: + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (supported_mode) + *supported_mode = g_steal_pointer (&tmp_supported_mode); + if (supported_mt) + *supported_mt = g_steal_pointer (&tmp_supported_mt); + if (supported_bm) + *supported_bm = g_steal_pointer (&tmp_supported_bm); + if (supported_ds) + *supported_ds = g_steal_pointer (&tmp_supported_ds); + if (supported_bfr) + *supported_bfr = g_steal_pointer (&tmp_supported_bfr); + + return TRUE; +} + +/*****************************************************************************/ +/* ^SXRAT test parser + * + * Example (ELS61-E2): + * AT^SXRAT=? + * ^SXRAT: (0-6),(0,2,3),(0,2,3) + */ + +gboolean +mm_cinterion_parse_sxrat_test (const gchar *response, + GArray **supported_rat, + GArray **supported_pref1, + GArray **supported_pref2, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + GArray *tmp_supported_rat = NULL; + GArray *tmp_supported_pref1 = NULL; + GArray *tmp_supported_pref2 = NULL; + + if (!response) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response"); + return FALSE; + } + + r = g_regex_new ("\\^SXRAT:\\s*\\(([^\\)]*)\\),\\(([^\\)]*)\\)(,\\(([^\\)]*)\\))?(?:\\r\\n)?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, + 0, NULL); + + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + + if (!inner_error && g_match_info_matches (match_info)) { + if (supported_rat) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 1); + tmp_supported_rat = mm_parse_uint_list (str, &inner_error); + + if (inner_error) + goto out; + } + if (supported_pref1) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 2); + tmp_supported_pref1 = mm_parse_uint_list (str, &inner_error); + + if (inner_error) + goto out; + } + if (supported_pref2) { + g_autofree gchar *str = NULL; + + /* this match is optional */ + str = mm_get_string_unquoted_from_match_info (match_info, 4); + if (str) { + tmp_supported_pref2 = mm_parse_uint_list (str, &inner_error); + + if (inner_error) + goto out; + } + } + } + +out: + + if (inner_error) { + g_clear_pointer (&tmp_supported_rat, g_array_unref); + g_clear_pointer (&tmp_supported_pref1, g_array_unref); + g_clear_pointer (&tmp_supported_pref2, g_array_unref); + g_propagate_error (error, inner_error); + return FALSE; + } + + if (supported_rat) + *supported_rat = tmp_supported_rat; + if (supported_pref1) + *supported_pref1 = tmp_supported_pref1; + if (supported_pref2) + *supported_pref2 = tmp_supported_pref2; + + return TRUE; +} + +/*****************************************************************************/ +/* Build Cinterion-specific band value */ + +gboolean +mm_cinterion_build_band (GArray *bands, + guint *supported, + gboolean only_2g, + MMCinterionRadioBandFormat format, + MMCinterionModemFamily modem_family, + guint *out_band, + GError **error) +{ + guint band[MM_CINTERION_RB_BLOCK_N] = { 0 }; + + if (format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) { + /* The special case of ANY should be treated separately. */ + if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) { + if (supported) + band[MM_CINTERION_RB_BLOCK_LEGACY] = supported[MM_CINTERION_RB_BLOCK_LEGACY]; + } else { + guint i; + + for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) { + guint j; + + for (j = 0; j < bands->len; j++) { + if (g_array_index (bands, MMModemBand, j) == cinterion_bands[i].mm_band) { + band[MM_CINTERION_RB_BLOCK_LEGACY] |= cinterion_bands[i].cinterion_band_flag; + break; + } + } + } + + /* 2G-only modems only support a subset of the possible band + * combinations. Detect it early and error out. + */ + if (only_2g && !VALIDATE_2G_BAND (band[MM_CINTERION_RB_BLOCK_LEGACY])) + band[MM_CINTERION_RB_BLOCK_LEGACY] = 0; + } + + if (band[MM_CINTERION_RB_BLOCK_LEGACY] == 0) { + g_autofree gchar *bands_string = NULL; + + bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands->data, bands->len); + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "The given band combination is not supported: '%s'", + bands_string); + return FALSE; + } + + } else { /* format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE */ + if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) { + if (supported) + memcpy (band, supported, sizeof (guint) * MM_CINTERION_RB_BLOCK_N); + } else { + guint i; + const CinterionBandEx *ref_bands; + guint nb_ref_bands; + + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) { + ref_bands = cinterion_bands_imt; + nb_ref_bands = G_N_ELEMENTS (cinterion_bands_imt); + } else { + ref_bands = cinterion_bands_ex; + nb_ref_bands = G_N_ELEMENTS (cinterion_bands_ex); + } + + for (i = 0; i < nb_ref_bands; i++) { + guint j; + + for (j = 0; j < bands->len; j++) { + if (g_array_index (bands, MMModemBand, j) == ref_bands[i].mm_band) { + band[ref_bands[i].cinterion_band_block] |= ref_bands[i].cinterion_band_flag; + break; + } + } + } + } + + /* this modem family does not allow disabling all bands in a given technology through this command */ + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT && + (!band[MM_CINTERION_RB_BLOCK_GSM] || + !band[MM_CINTERION_RB_BLOCK_UMTS] || + !band[MM_CINTERION_RB_BLOCK_LTE_LOW])) { + g_autofree gchar *bands_string = NULL; + + bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands->data, bands->len); + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "The given band combination is not supported: '%s'", + bands_string); + return FALSE; + } + } + + memcpy (out_band, band, sizeof (guint) * MM_CINTERION_RB_BLOCK_N); + return TRUE; +} + +/*****************************************************************************/ +/* Single ^SIND response parser */ + +gboolean +mm_cinterion_parse_sind_response (const gchar *response, + gchar **description, + guint *mode, + guint *value, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + guint errors = 0; + + if (!response) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response"); + return FALSE; + } + + r = g_regex_new ("\\^SIND:\\s*(.*),(\\d+),(\\d+)(\\r\\n)?", 0, 0, NULL); + g_assert (r != NULL); + + if (g_regex_match (r, response, 0, &match_info)) { + if (description) { + *description = mm_get_string_unquoted_from_match_info (match_info, 1); + if (*description == NULL) + errors++; + } + if (mode && !mm_get_uint_from_match_info (match_info, 2, mode)) + errors++; + if (value && !mm_get_uint_from_match_info (match_info, 3, value)) + errors++; + } else + errors++; + + if (errors > 0) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed parsing ^SIND response"); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ +/* ^SWWAN read parser + * + * Description: Parses <cid>, <state>[, <WWAN adapter>] or CME ERROR from SWWAN. + * + * The method returns a MMSwwanState with the connection status of a single + * PDP context, the one being queried via the cid given as input. + * + * Note that we use CID for matching because the WWAN adapter field is optional + * it seems. + * + * Read Command + * AT^SWWAN? + * Response(s) + * [^SWWAN: <cid>, <state>[, <WWAN adapter>]] + * [^SWWAN: ...] + * OK + * ERROR + * +CME ERROR: <err> + * + * Examples: + * OK - If no WWAN connection is active, then read command just returns OK + * ^SWWAN: 3,1,1 - 3rd PDP Context, Activated, First WWAN Adaptor + * +CME ERROR: ? - + */ + +enum { + MM_SWWAN_STATE_DISCONNECTED = 0, + MM_SWWAN_STATE_CONNECTED = 1, +}; + +MMBearerConnectionStatus +mm_cinterion_parse_swwan_response (const gchar *response, + guint cid, + gpointer log_object, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + MMBearerConnectionStatus status; + + g_assert (response); + + /* If no WWAN connection is active, then ^SWWAN read command just returns OK + * (which we receive as an empty string) */ + if (!response[0]) + return MM_BEARER_CONNECTION_STATUS_DISCONNECTED; + + if (!g_str_has_prefix (response, "^SWWAN:")) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse ^SWWAN response: '%s'", response); + return MM_BEARER_CONNECTION_STATUS_UNKNOWN; + } + + r = g_regex_new ("\\^SWWAN:\\s*(\\d+),\\s*(\\d+)(?:,\\s*(\\d+))?(?:\\r\\n)?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r != NULL); + + status = MM_BEARER_CONNECTION_STATUS_UNKNOWN; + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + while (!inner_error && g_match_info_matches (match_info)) { + guint read_state; + guint read_cid; + + if (!mm_get_uint_from_match_info (match_info, 1, &read_cid)) + mm_obj_warn (log_object, "couldn't read cid in ^SWWAN response: %s", response); + else if (!mm_get_uint_from_match_info (match_info, 2, &read_state)) + mm_obj_warn (log_object, "couldn't read state in ^SWWAN response: %s", response); + else if (read_cid == cid) { + if (read_state == MM_SWWAN_STATE_CONNECTED) { + status = MM_BEARER_CONNECTION_STATUS_CONNECTED; + break; + } + if (read_state == MM_SWWAN_STATE_DISCONNECTED) { + status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED; + break; + } + mm_obj_warn (log_object, "invalid state read in ^SWWAN response: %u", read_state); + break; + } + g_match_info_next (match_info, &inner_error); + } + + if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No state returned for CID %u", cid); + + return status; +} + +/*****************************************************************************/ +/* ^SGAUTH response parser */ + +/* at^sgauth? + * ^SGAUTH: 1,2,"vf" + * ^SGAUTH: 3,0,"" + * ^SGAUTH: 4,0 + * + * OK + */ + +gboolean +mm_cinterion_parse_sgauth_response (const gchar *response, + guint cid, + MMBearerAllowedAuth *out_auth, + gchar **out_username, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + r = g_regex_new ("\\^SGAUTH:\\s*(\\d+),(\\d+),?\"?([a-zA-Z0-9_-]+)?\"?", 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL); + while (g_match_info_matches (match_info)) { + guint sgauth_cid = 0; + + if (mm_get_uint_from_match_info (match_info, 1, &sgauth_cid) && + (sgauth_cid == cid)) { + guint cinterion_auth_type = 0; + + mm_get_uint_from_match_info (match_info, 2, &cinterion_auth_type); + *out_auth = mm_auth_type_from_cinterion_auth_type (cinterion_auth_type); + *out_username = mm_get_string_unquoted_from_match_info (match_info, 3); + return TRUE; + } + g_match_info_next (match_info, NULL); + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "Auth settings for context %u not found", cid); + return FALSE; +} + +/*****************************************************************************/ +/* ^SMONG response parser */ + +static gboolean +get_access_technology_from_smong_gprs_status (guint gprs_status, + MMModemAccessTechnology *out, + GError **error) +{ + switch (gprs_status) { + case 0: + *out = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + return TRUE; + case 1: + case 2: + *out = MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + return TRUE; + case 3: + case 4: + *out = MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + return TRUE; + default: + break; + } + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_INVALID_ARGS, + "Couldn't get network capabilities, " + "unsupported GPRS status value: '%u'", + gprs_status); + return FALSE; +} + +gboolean +mm_cinterion_parse_smong_response (const gchar *response, + MMModemAccessTechnology *access_tech, + GError **error) +{ + guint value = 0; + GError *inner_error = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GRegex) regex = NULL; + + /* The AT^SMONG command returns a cell info table, where the second + * column identifies the "GPRS status", which is exactly what we want. + * So we'll try to read that second number in the values row. + * + * AT^SMONG + * GPRS Monitor + * BCCH G PBCCH PAT MCC MNC NOM TA RAC # Cell # + * 0776 1 - - 214 03 2 00 01 + * OK + */ + regex = g_regex_new (".*GPRS Monitor(?:\r\n)*" + "BCCH\\s*G.*\\r\\n" + "\\s*(\\d+)\\s*(\\d+)\\s*", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, + 0, NULL); + g_assert (regex); + + g_regex_match_full (regex, response, strlen (response), 0, 0, &match_info, &inner_error); + + if (inner_error) { + g_prefix_error (&inner_error, "Failed to match AT^SMONG response: "); + g_propagate_error (error, inner_error); + return FALSE; + } + + if (!g_match_info_matches (match_info) || !mm_get_uint_from_match_info (match_info, 2, &value)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't read 'GPRS status' field from AT^SMONG response"); + return FALSE; + } + + return get_access_technology_from_smong_gprs_status (value, access_tech, error); +} + +/*****************************************************************************/ +/* ^SIND psinfo helper */ + +MMModemAccessTechnology +mm_cinterion_get_access_technology_from_sind_psinfo (guint val, + gpointer log_object) +{ + switch (val) { + case 0: + return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + case 1: + case 2: + return MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + case 3: + case 4: + return MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + case 5: + case 6: + return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + case 7: + case 8: + return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; + case 9: + case 10: + return (MM_MODEM_ACCESS_TECHNOLOGY_HSDPA | MM_MODEM_ACCESS_TECHNOLOGY_HSUPA); + case 16: + case 17: + return MM_MODEM_ACCESS_TECHNOLOGY_LTE; + default: + mm_obj_dbg (log_object, "unable to identify access technology from psinfo reported value: %u", val); + return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + } +} + +/*****************************************************************************/ +/* ^SLCC psinfo helper */ + +GRegex * +mm_cinterion_get_slcc_regex (void) +{ + /* The list of active calls displayed with this URC will always be terminated + * with an empty line preceded by prefix "^SLCC: ", in order to indicate the end + * of the list. + */ + return g_regex_new ("\\r\\n(\\^SLCC: .*\\r\\n)*\\^SLCC: \\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +static void +cinterion_call_info_free (MMCallInfo *info) +{ + if (!info) + return; + g_free (info->number); + g_slice_free (MMCallInfo, info); +} + +gboolean +mm_cinterion_parse_slcc_list (const gchar *str, + gpointer log_object, + GList **out_list, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GList *list = NULL; + GError *inner_error = NULL; + + static const MMCallDirection cinterion_call_direction[] = { + [0] = MM_CALL_DIRECTION_OUTGOING, + [1] = MM_CALL_DIRECTION_INCOMING, + }; + + static const MMCallState cinterion_call_state[] = { + [0] = MM_CALL_STATE_ACTIVE, + [1] = MM_CALL_STATE_HELD, + [2] = MM_CALL_STATE_DIALING, /* Dialing (MOC) */ + [3] = MM_CALL_STATE_RINGING_OUT, /* Alerting (MOC) */ + [4] = MM_CALL_STATE_RINGING_IN, /* Incoming (MTC) */ + [5] = MM_CALL_STATE_WAITING, /* Waiting (MTC) */ + }; + + g_assert (out_list); + + /* + * 1 2 3 4 5 6 7 8 9 + * ^SLCC: <idx>, <dir>, <stat>, <mode>, <mpty>, <Reserved>[, <number>, <type>[,<alpha>]] + * [^SLCC: <idx>, <dir>, <stat>, <mode>, <mpty>, <Reserved>[, <number>, <type>[,<alpha>]]] + * [... ] + * ^SLCC : + */ + + r = g_regex_new ("\\^SLCC:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)" /* mandatory fields */ + "(?:,\\s*([^,]*),\\s*(\\d+)" /* number and type */ + "(?:,\\s*([^,]*)" /* alpha */ + ")?)?$", + G_REGEX_RAW | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, + G_REGEX_MATCH_NEWLINE_CRLF, + NULL); + g_assert (r != NULL); + + g_regex_match_full (r, str, strlen (str), 0, 0, &match_info, &inner_error); + if (inner_error) + goto out; + + /* Parse the results */ + while (g_match_info_matches (match_info)) { + MMCallInfo *call_info; + guint aux; + + call_info = g_slice_new0 (MMCallInfo); + + if (!mm_get_uint_from_match_info (match_info, 1, &call_info->index)) { + mm_obj_warn (log_object, "couldn't parse call index from ^SLCC line"); + goto next; + } + + if (!mm_get_uint_from_match_info (match_info, 2, &aux) || + (aux >= G_N_ELEMENTS (cinterion_call_direction))) { + mm_obj_warn (log_object, "couldn't parse call direction from ^SLCC line"); + goto next; + } + call_info->direction = cinterion_call_direction[aux]; + + if (!mm_get_uint_from_match_info (match_info, 3, &aux) || + (aux >= G_N_ELEMENTS (cinterion_call_state))) { + mm_obj_warn (log_object, "couldn't parse call state from ^SLCC line"); + goto next; + } + call_info->state = cinterion_call_state[aux]; + + if (g_match_info_get_match_count (match_info) >= 8) + call_info->number = mm_get_string_unquoted_from_match_info (match_info, 7); + + list = g_list_append (list, call_info); + call_info = NULL; + + next: + cinterion_call_info_free (call_info); + g_match_info_next (match_info, NULL); + } + +out: + if (inner_error) { + mm_cinterion_call_info_list_free (list); + g_propagate_error (error, inner_error); + return FALSE; + } + + *out_list = list; + + return TRUE; +} + +void +mm_cinterion_call_info_list_free (GList *call_info_list) +{ + g_list_free_full (call_info_list, (GDestroyNotify) cinterion_call_info_free); +} + +/*****************************************************************************/ +/* +CTZU URC helpers */ + +GRegex * +mm_cinterion_get_ctzu_regex (void) +{ + /* + * From PLS-8 AT command spec: + * +CTZU:<nitzUT>, <nitzTZ>[, <nitzDST>] + * E.g.: + * +CTZU: "19/07/09,10:19:15",+08,1 + */ + + return g_regex_new ("\\r\\n\\+CTZU:\\s*\"(\\d+)\\/(\\d+)\\/(\\d+),(\\d+):(\\d+):(\\d+)\",([\\-\\+\\d]+)(?:,(\\d+))?(?:\\r\\n)?", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +gboolean +mm_cinterion_parse_ctzu_urc (GMatchInfo *match_info, + gchar **iso8601p, + MMNetworkTimezone **tzp, + GError **error) +{ + gboolean ret = TRUE; + guint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, dst = 0; + gint tz = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &year) || + !mm_get_uint_from_match_info (match_info, 2, &month) || + !mm_get_uint_from_match_info (match_info, 3, &day) || + !mm_get_uint_from_match_info (match_info, 4, &hour) || + !mm_get_uint_from_match_info (match_info, 5, &minute) || + !mm_get_uint_from_match_info (match_info, 6, &second) || + !mm_get_int_from_match_info (match_info, 7, &tz)) { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse +CTZU URC"); + return FALSE; + } + + /* adjust year */ + if (year < 100) + year += 2000; + + /* + * tz = timezone offset in 15 minute intervals + */ + if (iso8601p) { + /* Return ISO-8601 format date/time string */ + *iso8601p = mm_new_iso8601_time (year, month, day, hour, + minute, second, + TRUE, tz * 15, + error); + ret = (*iso8601p != NULL); + } + + if (tzp) { + *tzp = mm_network_timezone_new (); + mm_network_timezone_set_offset (*tzp, tz * 15); + } + + /* dst flag is optional in the URC + * + * tz = timezone offset in 15 minute intervals + * dst = daylight adjustment, 0 = none, 1 = 1 hour, 2 = 2 hours + */ + if (tzp && mm_get_uint_from_match_info (match_info, 8, &dst)) + mm_network_timezone_set_dst_offset (*tzp, dst * 60); + + return ret; +} + +/*****************************************************************************/ +/* ^SMONI response parser */ + +gboolean +mm_cinterion_parse_smoni_query_response (const gchar *response, + MMCinterionRadioGen *out_tech, + gdouble *out_rssi, + gdouble *out_ecn0, + gdouble *out_rscp, + gdouble *out_rsrp, + gdouble *out_rsrq, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GRegex) pre = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GMatchInfo) match_info_pre = NULL; + GError *inner_error = NULL; + MMCinterionRadioGen tech = MM_CINTERION_RADIO_GEN_NONE; + gdouble rssi = -G_MAXDOUBLE; + gdouble ecn0 = -G_MAXDOUBLE; + gdouble rscp = -G_MAXDOUBLE; + gdouble rsrq = -G_MAXDOUBLE; + gdouble rsrp = -G_MAXDOUBLE; + gboolean success = FALSE; + + g_assert (out_tech); + g_assert (out_rssi); + g_assert (out_ecn0); + g_assert (out_rscp); + g_assert (out_rsrp); + g_assert (out_rsrq); + g_assert (out_rssi); + + /* Possible Responses: + * 2G + * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,Conn_state // registered + * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,ARFCN,TS,timAdv,dBm,Q,ChMod // searching + * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,PWR,RXLev,ARFCN,TS,timAdv,dBm,Q,ChMod // limsrv + * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,ARFCN,TS,timAdv,dBm,Q,ChMod // dedicated channel + * + * ^SMONI: 2G,71,-61,262,02,0143,83BA,33,33,3,6,G,NOCONN + * ^^^ + * ^SMONI: 2G,SEARCH,SEARCH + * ^SMONI: 2G,673,-89,262,07,4EED,A500,16,16,7,4,G,5,-107,LIMSRV + * ^^^ ^^^^ RXLev dBm + * ^SMONI: 2G,673,-80,262,07,4EED,A500,35,35,7,4,G,643,4,0,-80,0,S_FR + * ^^^ ^^^ dBm: Receiving level of the traffic channel carrier in dBm + * BCCH: Receiving level of the BCCH carrier in dBm (level is limited from -110dBm to -47dBm) + * -> rssi for 2G, directly without mm_3gpp_rxlev_to_rssi + * + * + * 3G + * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,,Conn_state", + * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA", + * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA", + * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA", + * + * ^SMONI: 3G,10564,296,-7.5,-79,262,02,0143,00228FF,-92,-78,NOCONN + * ^^^^ ^^^ + * ^SMONI: 3G,SEARCH,SEARCH + * ^SMONI: 3G,10564,96,-7.5,-79,262,02,0143,00228FF,-92,-78,LIMSRV + * ^^^^ ^^^ + * ^SMONI: 3G,10737,131,-5,-93,260,01,7D3D,C80BC9A,--,--,----,---,-,-5,-93,0,01,06 + * ^^ ^^^ + * RSCP: Received Signal Code Power in dBm -> no need for mm_3gpp_rscp_level_to_rscp + * EC/n0: EC/n0 Carrier to noise ratio in dB = measured Ec/Io value in dB. Please refer to 3GPP 25.133, section 9.1.2.3, Table 9.9 for details on the mapping from EC/n0 to EC/Io. + * -> direct value, without need for mm_3gpp_ecn0_level_to_ecio + * + * + * 4G + * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state + * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state + * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state + * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,TX_power,RSRP,RSRQ,Conn_state + * + * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-94,-7,NOCONN + * ^^^ ^^ + * ^SMONI: 4G,SEARCH + * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-94,-7,LIMSRV + * ^^^ ^^ + * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,90,-94,-7,CONN + * ^^^ ^^ + * RSRP Reference Signal Received Power (see 3GPP 36.214 Section 5.1.1.) -> directly the value without mm_3gpp_rsrq_level_to_rsrp + * RSRQ Reference Signal Received Quality (see 3GPP 36.214 Section 5.1.2.) -> directly the value without mm_3gpp_rsrq_level_to_rsrq + */ + if (g_regex_match_simple ("\\^SMONI:\\s*[234]G,SEARCH", response, 0, 0)) { + success = TRUE; + goto out; + } + pre = g_regex_new ("\\^SMONI:\\s*([234])", 0, 0, NULL); + g_assert (pre != NULL); + g_regex_match_full (pre, response, strlen (response), 0, 0, &match_info_pre, &inner_error); + if (!inner_error && g_match_info_matches (match_info_pre)) { + if (!mm_get_uint_from_match_info (match_info_pre, 1, &tech)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read tech"); + goto out; + } + #define FLOAT "([-+]?[0-9]+\\.?[0-9]*)" + switch (tech) { + case MM_CINTERION_RADIO_GEN_2G: + r = g_regex_new ("\\^SMONI:\\s*2G,(\\d+),"FLOAT, 0, 0, NULL); + g_assert (r != NULL); + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + /* skip ARFCN */ + if (!mm_get_double_from_match_info (match_info, 2, &rssi)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read BCCH=rssi"); + goto out; + } + } + break; + case MM_CINTERION_RADIO_GEN_3G: + r = g_regex_new ("\\^SMONI:\\s*3G,(\\d+),(\\d+),"FLOAT","FLOAT, 0, 0, NULL); + g_assert (r != NULL); + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + /* skip UARFCN */ + /* skip PSC (Primary scrambling code) */ + if (!mm_get_double_from_match_info (match_info, 3, &ecn0)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read EcN0"); + goto out; + } + if (!mm_get_double_from_match_info (match_info, 4, &rscp)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSCP"); + goto out; + } + } + break; + case MM_CINTERION_RADIO_GEN_4G: + r = g_regex_new ("\\^SMONI:\\s*4G,(\\d+),(\\d+),(\\d+),(\\d+),(\\w+),(\\d+),(\\d+),(\\w+),(\\w+),(\\d+),([^,]*),"FLOAT","FLOAT, 0, 0, NULL); + g_assert (r != NULL); + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + /* skip EARFCN */ + /* skip Band */ + /* skip DL bandwidth */ + /* skip UL bandwidth */ + /* skip Mode */ + /* skip MCC */ + /* skip MNC */ + /* skip TAC */ + /* skip Global Cell ID */ + /* skip Physical Cell ID */ + /* skip Srxlev/TX_power */ + if (!mm_get_double_from_match_info (match_info, 12, &rsrp)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRQ"); + goto out; + } + if (!mm_get_double_from_match_info (match_info, 13, &rsrq)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRP"); + goto out; + } + } + break; + case MM_CINTERION_RADIO_GEN_NONE: + default: + goto out; + } + #undef FLOAT + success = TRUE; + } + +out: + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (!success) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse ^SMONI response: %s", response); + return FALSE; + } + + *out_tech = tech; + *out_rssi = rssi; + *out_rscp = rscp; + *out_ecn0 = ecn0; + *out_rsrq = rsrq; + *out_rsrp = rsrp; + return TRUE; +} + +/*****************************************************************************/ +/* Get extended signal information */ + +gboolean +mm_cinterion_smoni_response_to_signal_info (const gchar *response, + MMSignal **out_gsm, + MMSignal **out_umts, + MMSignal **out_lte, + GError **error) +{ + MMCinterionRadioGen tech = MM_CINTERION_RADIO_GEN_NONE; + gdouble rssi = MM_SIGNAL_UNKNOWN; + gdouble ecn0 = MM_SIGNAL_UNKNOWN; + gdouble rscp = MM_SIGNAL_UNKNOWN; + gdouble rsrq = MM_SIGNAL_UNKNOWN; + gdouble rsrp = MM_SIGNAL_UNKNOWN; + MMSignal *gsm = NULL; + MMSignal *umts = NULL; + MMSignal *lte = NULL; + + if (!mm_cinterion_parse_smoni_query_response (response, + &tech, &rssi, + &ecn0, &rscp, + &rsrp, &rsrq, + error)) + return FALSE; + + switch (tech) { + case MM_CINTERION_RADIO_GEN_2G: + gsm = mm_signal_new (); + mm_signal_set_rssi (gsm, rssi); + break; + case MM_CINTERION_RADIO_GEN_3G: + umts = mm_signal_new (); + mm_signal_set_rscp (umts, rscp); + mm_signal_set_ecio (umts, ecn0); /* UMTS EcIo (assumed EcN0) */ + break; + case MM_CINTERION_RADIO_GEN_4G: + lte = mm_signal_new (); + mm_signal_set_rsrp (lte, rsrp); + mm_signal_set_rsrq (lte, rsrq); + break; + case MM_CINTERION_RADIO_GEN_NONE: /* not registered, searching */ + break; /* no error case */ + default: /* should not happen, so if it does, error */ + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't build detailed signal info"); + return FALSE; + } + + if (out_gsm) + *out_gsm = gsm; + if (out_umts) + *out_umts = umts; + if (out_lte) + *out_lte = lte; + + return TRUE; +} + +/*****************************************************************************/ +/* provider cfg information to CID number for EPS initial settings */ + +/* + * at^scfg="MEopMode/Prov/Cfg" + * ^SCFG: "MEopMode/Prov/Cfg","vdfde" + * ^SCFG: "MEopMode/Prov/Cfg","attus" + * ^SCFG: "MEopMode/Prov/Cfg","2" -> PLS8-X vzw + * ^SCFG: "MEopMode/Prov/Cfg","vzwdcus" -> PLAS9-x vzw + * ^SCFG: "MEopMode/Prov/Cfg","tmode" -> t-mob germany + * OK + */ +gboolean +mm_cinterion_provcfg_response_to_cid (const gchar *response, + MMCinterionModemFamily modem_family, + MMModemCharset charset, + gpointer log_object, + gint *cid, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *mno = NULL; + GError *inner_error = NULL; + + r = g_regex_new ("\\^SCFG:\\s*\"MEopMode/Prov/Cfg\",\\s*\"([0-9a-zA-Z*]*)\"", 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + + if (inner_error) { + g_prefix_error (&inner_error, "Failed to match Prov/Cfg response: "); + g_propagate_error (error, inner_error); + return FALSE; + } + + if (!g_match_info_matches (match_info)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't match Prov/Cfg response"); + return FALSE; + } + + mno = mm_get_string_unquoted_from_match_info (match_info, 1); + if (mno && modem_family == MM_CINTERION_MODEM_FAMILY_IMT) { + gchar *mno_utf8; + + mno_utf8 = mm_modem_charset_str_to_utf8 (mno, -1, charset, FALSE, error); + if (!mno_utf8) + return FALSE; + g_free (mno); + mno = mno_utf8; + } + mm_obj_dbg (log_object, "current mno: %s", mno ? mno : "none"); + + /* for Cinterion LTE modules, some CID numbers have special meaning. + * This is dictated by the chipset and by the MNO: + * - the chipset uses a special one, CID 1, as a LTE combined attach chipset + * - the MNOs can define the sequence and number of APN to be used for their network. + * This takes priority over the chipset preferences, and therefore for some of them + * the CID for the initial EPS context must be changed. + */ + if (g_strcmp0 (mno, "2") == 0 || g_strcmp0 (mno, "vzwdcus") == 0) + *cid = 3; + else if (g_strcmp0 (mno, "tmode") == 0) + *cid = 2; + else + *cid = 1; + return TRUE; +} + +/*****************************************************************************/ +/* Auth related helpers */ + +typedef enum { + BEARER_CINTERION_AUTH_UNKNOWN = -1, + BEARER_CINTERION_AUTH_NONE = 0, + BEARER_CINTERION_AUTH_PAP = 1, + BEARER_CINTERION_AUTH_CHAP = 2, + BEARER_CINTERION_AUTH_MSCHAPV2 = 3, +} BearerCinterionAuthType; + +static BearerCinterionAuthType +parse_auth_type (MMBearerAllowedAuth mm_auth) +{ + switch (mm_auth) { + case MM_BEARER_ALLOWED_AUTH_NONE: + return BEARER_CINTERION_AUTH_NONE; + case MM_BEARER_ALLOWED_AUTH_PAP: + return BEARER_CINTERION_AUTH_PAP; + case MM_BEARER_ALLOWED_AUTH_CHAP: + return BEARER_CINTERION_AUTH_CHAP; + case MM_BEARER_ALLOWED_AUTH_MSCHAPV2: + return BEARER_CINTERION_AUTH_MSCHAPV2; + case MM_BEARER_ALLOWED_AUTH_UNKNOWN: + case MM_BEARER_ALLOWED_AUTH_MSCHAP: + case MM_BEARER_ALLOWED_AUTH_EAP: + default: + return BEARER_CINTERION_AUTH_UNKNOWN; + } +} + +MMBearerAllowedAuth +mm_auth_type_from_cinterion_auth_type (guint cinterion_auth) +{ + switch (cinterion_auth) { + case BEARER_CINTERION_AUTH_NONE: + return MM_BEARER_ALLOWED_AUTH_NONE; + case BEARER_CINTERION_AUTH_PAP: + return MM_BEARER_ALLOWED_AUTH_PAP; + case BEARER_CINTERION_AUTH_CHAP: + return MM_BEARER_ALLOWED_AUTH_CHAP; + default: + return MM_BEARER_ALLOWED_AUTH_UNKNOWN; + } +} + +/* Cinterion authentication is done with the command AT^SGAUTH, + whose syntax depends on the modem family, as follow: + - AT^SGAUTH=<cid>[, <auth_type>[, <user>, <passwd>]] for the IMT family + - AT^SGAUTH=<cid>[, <auth_type>[, <passwd>, <user>]] for the rest */ +gchar * +mm_cinterion_build_auth_string (gpointer log_object, + MMCinterionModemFamily modem_family, + MMBearerProperties *config, + guint cid) +{ + MMBearerAllowedAuth auth; + BearerCinterionAuthType encoded_auth = BEARER_CINTERION_AUTH_UNKNOWN; + gboolean has_user; + gboolean has_passwd; + const gchar *user; + const gchar *passwd; + g_autofree gchar *quoted_user = NULL; + g_autofree gchar *quoted_passwd = NULL; + + user = mm_bearer_properties_get_user (config); + passwd = mm_bearer_properties_get_password (config); + auth = mm_bearer_properties_get_allowed_auth (config); + + has_user = (user && user[0]); + has_passwd = (passwd && passwd[0]); + encoded_auth = parse_auth_type (auth); + + /* When 'none' requested, we won't require user/password */ + if (encoded_auth == BEARER_CINTERION_AUTH_NONE) { + if (has_user || has_passwd) + mm_obj_warn (log_object, "APN user/password given but 'none' authentication requested"); + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) + return g_strdup_printf ("^SGAUTH=%u,%d,\"\",\"\"", cid, encoded_auth); + return g_strdup_printf ("^SGAUTH=%u,%d", cid, encoded_auth); + } + + /* No explicit auth type requested? */ + if (encoded_auth == BEARER_CINTERION_AUTH_UNKNOWN) { + /* If no user/passwd given, do nothing */ + if (!has_user && !has_passwd) + return NULL; + + /* If user/passwd given, default to CHAP (more common than PAP) */ + mm_obj_dbg (log_object, "APN user/password given but no authentication type explicitly requested: defaulting to 'CHAP'"); + encoded_auth = BEARER_CINTERION_AUTH_CHAP; + } + + quoted_user = mm_port_serial_at_quote_string (user ? user : ""); + quoted_passwd = mm_port_serial_at_quote_string (passwd ? passwd : ""); + + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) + return g_strdup_printf ("^SGAUTH=%u,%d,%s,%s", + cid, + encoded_auth, + quoted_user, + quoted_passwd); + + return g_strdup_printf ("^SGAUTH=%u,%d,%s,%s", + cid, + encoded_auth, + quoted_passwd, + quoted_user); +} + +/*****************************************************************************/ +/* ^SXRAT set command builder */ + +/* Index of the array is the centerion-specific sxrat value */ +static const MMModemMode sxrat_combinations[] = { + [0] = ( MM_MODEM_MODE_2G ), + [1] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ), + [2] = ( MM_MODEM_MODE_3G ), + [3] = ( MM_MODEM_MODE_4G ), + [4] = ( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ), + [5] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ), + [6] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ), +}; + +static gboolean +append_sxrat_rat_value (GString *str, + MMModemMode mode, + GError **error) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (sxrat_combinations); i++) { + if (sxrat_combinations[i] == mode) { + g_string_append_printf (str, "%u", i); + return TRUE; + } + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No AcT value matches requested mode"); + return FALSE; +} + +gchar * +mm_cinterion_build_sxrat_set_command (MMModemMode allowed, + MMModemMode preferred, + GError **error) +{ + GString *command; + + command = g_string_new ("^SXRAT="); + if (!append_sxrat_rat_value (command, allowed, error)) { + g_string_free (command, TRUE); + return NULL; + } + + if (preferred != MM_MODEM_MODE_NONE) { + if (mm_count_bits_set (preferred) != 1) { + *error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "AcT preferred value should be a single AcT"); + g_string_free (command, TRUE); + return NULL; + } + g_string_append (command, ","); + if (!append_sxrat_rat_value (command, preferred, error)) { + g_string_free (command, TRUE); + return NULL; + } + } + + return g_string_free (command, FALSE); +} diff --git a/src/plugins/cinterion/mm-modem-helpers-cinterion.h b/src/plugins/cinterion/mm-modem-helpers-cinterion.h new file mode 100644 index 00000000..3155d0c1 --- /dev/null +++ b/src/plugins/cinterion/mm-modem-helpers-cinterion.h @@ -0,0 +1,211 @@ +/* -*- 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) 2014 Aleksander Morgado <aleksander@aleksander.es> + * Copyright (C) 2016 Trimble Navigation Limited + * Copyright (C) 2016 Matthew Stanger <matthew_stanger@trimble.com> + * Copyright (C) 2019 Purism SPC + */ + +#ifndef MM_MODEM_HELPERS_CINTERION_H +#define MM_MODEM_HELPERS_CINTERION_H + +#include <glib.h> + +#include <ModemManager.h> +#include <mm-base-bearer.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +typedef enum { + MM_CINTERION_MODEM_FAMILY_DEFAULT = 0, + MM_CINTERION_MODEM_FAMILY_IMT = 1, +} MMCinterionModemFamily; + +typedef enum { + MM_CINTERION_RADIO_BAND_FORMAT_SINGLE = 0, + MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE = 1, +} MMCinterionRadioBandFormat; + +typedef enum { + MM_CINTERION_RB_BLOCK_LEGACY = 0, + MM_CINTERION_RB_BLOCK_GSM = 0, + MM_CINTERION_RB_BLOCK_UMTS = 1, + MM_CINTERION_RB_BLOCK_LTE_LOW = 2, + MM_CINTERION_RB_BLOCK_LTE_HIGH = 3, + MM_CINTERION_RB_BLOCK_N = 4 +} MMCinterionRbBlock; + +typedef enum { + MM_CINTERION_RADIO_GEN_NONE = 0, + MM_CINTERION_RADIO_GEN_2G = 2, + MM_CINTERION_RADIO_GEN_3G = 3, + MM_CINTERION_RADIO_GEN_4G = 4, +} MMCinterionRadioGen; + +/*****************************************************************************/ +/* ^SCFG test parser */ + +gboolean mm_cinterion_parse_scfg_test (const gchar *response, + MMCinterionModemFamily modem_family, + MMModemCharset charset, + GArray **supported_bands, + MMCinterionRadioBandFormat *format, + GError **error); + +/*****************************************************************************/ +/* ^SCFG response parser */ + +gboolean mm_cinterion_parse_scfg_response (const gchar *response, + MMCinterionModemFamily modem_family, + MMModemCharset charset, + GArray **bands, + MMCinterionRadioBandFormat format, + GError **error); + +/*****************************************************************************/ +/* +CNMI test parser */ + +gboolean mm_cinterion_parse_cnmi_test (const gchar *response, + GArray **supported_mode, + GArray **supported_mt, + GArray **supported_bm, + GArray **supported_ds, + GArray **supported_bfr, + GError **error); + +/*****************************************************************************/ +/* ^SXRAT test parser */ + +gboolean mm_cinterion_parse_sxrat_test (const gchar *response, + GArray **supported_rat, + GArray **supported_pref1, + GArray **supported_pref2, + GError **error); + +/*****************************************************************************/ +/* Build Cinterion-specific band value */ + +gboolean mm_cinterion_build_band (GArray *bands, + guint *supported, + gboolean only_2g, + MMCinterionRadioBandFormat format, + MMCinterionModemFamily modem_family, + guint *out_band, + GError **error); + +/*****************************************************************************/ +/* Single ^SIND response parser */ + +gboolean mm_cinterion_parse_sind_response (const gchar *response, + gchar **description, + guint *mode, + guint *value, + GError **error); + +/*****************************************************************************/ +/* ^SWWAN response parser */ + +MMBearerConnectionStatus mm_cinterion_parse_swwan_response (const gchar *response, + guint swwan_index, + gpointer log_object, + GError **error); + +/*****************************************************************************/ +/* ^SGAUTH response parser */ + +gboolean mm_cinterion_parse_sgauth_response (const gchar *response, + guint cid, + MMBearerAllowedAuth *out_auth, + gchar **out_username, + GError **error); + +/*****************************************************************************/ +/* ^SMONG response parser */ + +gboolean mm_cinterion_parse_smong_response (const gchar *response, + MMModemAccessTechnology *access_tech, + GError **error); + +/*****************************************************************************/ +/* ^SIND psinfo helper */ + +MMModemAccessTechnology mm_cinterion_get_access_technology_from_sind_psinfo (guint val, + gpointer log_object); + +/*****************************************************************************/ +/* ^SLCC URC helpers */ + +GRegex *mm_cinterion_get_slcc_regex (void); + +/* MMCallInfo list management */ +gboolean mm_cinterion_parse_slcc_list (const gchar *str, + gpointer log_object, + GList **out_list, + GError **error); +void mm_cinterion_call_info_list_free (GList *call_info_list); + +/*****************************************************************************/ +/* +CTZU URC helpers */ + +GRegex *mm_cinterion_get_ctzu_regex (void); +gboolean mm_cinterion_parse_ctzu_urc (GMatchInfo *match_info, + gchar **iso8601p, + MMNetworkTimezone **tzp, + GError **error); + +/*****************************************************************************/ +/* ^SMONI helper */ + +gboolean mm_cinterion_parse_smoni_query_response (const gchar *response, + MMCinterionRadioGen *out_tech, + gdouble *out_rssi, + gdouble *out_ecn0, + gdouble *out_rscp, + gdouble *out_rsrp, + gdouble *out_rsrq, + GError **error); + +gboolean mm_cinterion_smoni_response_to_signal_info (const gchar *response, + MMSignal **out_gsm, + MMSignal **out_umts, + MMSignal **out_lte, + GError **error); + +/*****************************************************************************/ +/* ^SCFG="MEopMode/Prov/Cfg" helper */ + +gboolean mm_cinterion_provcfg_response_to_cid (const gchar *response, + MMCinterionModemFamily modem_family, + MMModemCharset charset, + gpointer log_object, + gint *cid, + GError **error); + +/*****************************************************************************/ +/* Auth related helpers */ + +MMBearerAllowedAuth mm_auth_type_from_cinterion_auth_type (guint cinterion_auth); + +gchar *mm_cinterion_build_auth_string (gpointer log_object, + MMCinterionModemFamily modem_family, + MMBearerProperties *config, + guint cid); + +/*****************************************************************************/ +/* ^SXRAT set command helper */ + +gchar *mm_cinterion_build_sxrat_set_command (MMModemMode allowed, + MMModemMode preferred, + GError **error); + +#endif /* MM_MODEM_HELPERS_CINTERION_H */ diff --git a/src/plugins/cinterion/mm-plugin-cinterion.c b/src/plugins/cinterion/mm-plugin-cinterion.c new file mode 100644 index 00000000..b0f3f992 --- /dev/null +++ b/src/plugins/cinterion/mm-plugin-cinterion.c @@ -0,0 +1,218 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2011 Ammonit Measurement GmbH + * Copyright (C) 2011 - 2012 Google Inc. + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-cinterion.h" +#include "mm-broadband-modem-cinterion.h" +#include "mm-log-object.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi-cinterion.h" +#endif + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim-cinterion.h" +#endif + +G_DEFINE_TYPE (MMPluginCinterion, mm_plugin_cinterion, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ +/* Custom init */ + +#define TAG_CINTERION_APP_PORT "cinterion-app-port" +#define TAG_CINTERION_MODEM_PORT "cinterion-modem-port" + +static gboolean +cinterion_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +sqport_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + MMPortProbe *probe; + const gchar *response; + + probe = g_task_get_source_object (task); + + /* Ignore errors, just avoid tagging */ + response = mm_port_serial_at_command_finish (port, res, NULL); + if (response) { + /* A valid reply to AT^SQPORT tells us this is an AT port already */ + mm_port_probe_set_result_at (probe, TRUE); + + if (strstr (response, "Application")) + g_object_set_data (G_OBJECT (probe), TAG_CINTERION_APP_PORT, GUINT_TO_POINTER (TRUE)); + else if (strstr (response, "Modem")) + g_object_set_data (G_OBJECT (probe), TAG_CINTERION_MODEM_PORT, GUINT_TO_POINTER (TRUE)); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +cinterion_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (probe, cancellable, callback, user_data); + + mm_port_serial_at_command ( + port, + "AT^SQPORT?", + 3, + FALSE, /* raw */ + FALSE, /* allow cached */ + cancellable, + (GAsyncReadyCallback) sqport_ready, + task); +} + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Cinterion modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_cinterion_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered Cinterion modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_cinterion_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_cinterion_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +static gboolean +grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE; + MMPortType ptype; + + ptype = mm_port_probe_get_port_type (probe); + + if (g_object_get_data (G_OBJECT (probe), TAG_CINTERION_APP_PORT)) { + mm_obj_dbg (self, "port '%s/%s' flagged as primary", + mm_port_probe_get_port_subsys (probe), + mm_port_probe_get_port_name (probe)); + pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY; + } else if (g_object_get_data (G_OBJECT (probe), TAG_CINTERION_MODEM_PORT)) { + mm_obj_dbg (self, "port '%s/%s' flagged as PPP", + mm_port_probe_get_port_subsys (probe), + mm_port_probe_get_port_name (probe)); + pflags = MM_PORT_SERIAL_AT_FLAG_PPP; + } + + return mm_base_modem_grab_port (modem, + mm_port_probe_peek_port (probe), + ptype, + pflags, + error); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", "wwan", NULL }; + static const gchar *vendor_strings[] = { "cinterion", "siemens", NULL }; + static const guint16 vendor_ids[] = { 0x1e2d, 0x0681, 0x1269, 0 }; + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (cinterion_custom_init), + .finish = G_CALLBACK (cinterion_custom_init_finish), + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_CINTERION, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + NULL)); +} + +static void +mm_plugin_cinterion_init (MMPluginCinterion *self) +{ +} + +static void +mm_plugin_cinterion_class_init (MMPluginCinterionClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = grab_port; +} diff --git a/src/plugins/cinterion/mm-plugin-cinterion.h b/src/plugins/cinterion/mm-plugin-cinterion.h new file mode 100644 index 00000000..a8a3b6bb --- /dev/null +++ b/src/plugins/cinterion/mm-plugin-cinterion.h @@ -0,0 +1,47 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2011 Ammonit Measurement GmbH + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#ifndef MM_PLUGIN_CINTERION_H +#define MM_PLUGIN_CINTERION_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_CINTERION (mm_plugin_cinterion_get_type ()) +#define MM_PLUGIN_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_CINTERION, MMPluginCinterion)) +#define MM_PLUGIN_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_CINTERION, MMPluginCinterionClass)) +#define MM_IS_PLUGIN_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_CINTERION)) +#define MM_IS_PLUGIN_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_CINTERION)) +#define MM_PLUGIN_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_CINTERION, MMPluginCinterionClass)) + +typedef struct { + MMPlugin parent; +} MMPluginCinterion; + +typedef struct { + MMPluginClass parent; +} MMPluginCinterionClass; + +GType mm_plugin_cinterion_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_CINTERION_H */ diff --git a/src/plugins/cinterion/mm-shared-cinterion.c b/src/plugins/cinterion/mm-shared-cinterion.c new file mode 100644 index 00000000..36cf60c9 --- /dev/null +++ b/src/plugins/cinterion/mm-shared-cinterion.c @@ -0,0 +1,1601 @@ +/* -*- 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) 2014 Ammonit Measurement GmbH + * Copyright (C) 2014 - 2018 Aleksander Morgado <aleksander@aleksander.es> + * Copyright (C) 2019 Purism SPC + */ + +#include <config.h> + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#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-shared-cinterion.h" +#include "mm-modem-helpers-cinterion.h" + +/*****************************************************************************/ +/* Private data context */ + +#define PRIVATE_TAG "shared-cinterion-private-tag" +static GQuark private_quark; + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED, +} FeatureSupport; + +typedef struct { + /* modem */ + MMIfaceModem *iface_modem_parent; + /* location */ + MMIfaceModemLocation *iface_modem_location_parent; + MMModemLocationSource supported_sources; + MMModemLocationSource enabled_sources; + FeatureSupport sgpss_support; + FeatureSupport sgpsc_support; + /* voice */ + MMIfaceModemVoice *iface_modem_voice_parent; + FeatureSupport slcc_support; + GRegex *slcc_regex; + /* time */ + MMIfaceModemTime *iface_modem_time_parent; + GRegex *ctzu_regex; +} Private; + +static void +private_free (Private *ctx) +{ + g_regex_unref (ctx->ctzu_regex); + g_regex_unref (ctx->slcc_regex); + g_slice_free (Private, ctx); +} + +static Private * +get_private (MMSharedCinterion *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_new (Private); + + priv->supported_sources = MM_MODEM_LOCATION_SOURCE_NONE; + priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE; + priv->sgpss_support = FEATURE_SUPPORT_UNKNOWN; + priv->sgpsc_support = FEATURE_SUPPORT_UNKNOWN; + priv->slcc_support = FEATURE_SUPPORT_UNKNOWN; + priv->slcc_regex = mm_cinterion_get_slcc_regex (); + priv->ctzu_regex = mm_cinterion_get_ctzu_regex (); + + /* Setup parent class' MMIfaceModem, MMIfaceModemLocation, MMIfaceModemVoice + * and MMIfaceModemTime */ + + g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_interface); + priv->iface_modem_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_interface (self); + + g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_location_interface); + priv->iface_modem_location_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_location_interface (self); + + g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_voice_interface); + priv->iface_modem_voice_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_voice_interface (self); + + g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_time_interface); + priv->iface_modem_time_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_time_interface (self); + + g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); + } + + return priv; +} + +/*****************************************************************************/ +/* Modem interface */ + +gboolean +mm_shared_cinterion_modem_reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +modem_reset_at_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_reset_at (GTask *task) +{ + MMSharedCinterion *self; + + self = g_task_get_source_object (task); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=1,1", + 3, + FALSE, + (GAsyncReadyCallback) modem_reset_at_ready, + task); +} + +static void +parent_modem_reset_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + if (!priv->iface_modem_parent->reset_finish (self, res, NULL)) { + modem_reset_at (task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_cinterion_modem_reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + task = g_task_new (self, NULL, callback, user_data); + + if (priv->iface_modem_parent->reset && + priv->iface_modem_parent->reset_finish) { + priv->iface_modem_parent->reset (self, + (GAsyncReadyCallback) parent_modem_reset_ready, + task); + return; + } + + modem_reset_at (task); +} + +/*****************************************************************************/ +/* GPS trace received */ + +static void +trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +/*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +MMModemLocationSource +mm_shared_cinterion_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize aux; + + aux = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource) aux; +} + +static void probe_gps_features (GTask *task); + +static void +sgpsc_test_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + if (!mm_base_modem_at_command_finish (self, res, NULL)) + priv->sgpsc_support = FEATURE_NOT_SUPPORTED; + else { + /* ^SGPSC supported! */ + priv->sgpsc_support = FEATURE_SUPPORTED; + /* It may happen that the modem was started with GPS already enabled, or + * maybe ModemManager got rebooted and it was left enabled before. We'll + * make sure that it is disabled when we initialize the modem. */ + mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=\"Engine\",\"0\"", 3, FALSE, NULL, NULL); + mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=\"Power/Antenna\",\"off\"", 3, FALSE, NULL, NULL); + mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=\"NMEA/Output\",\"off\"", 3, FALSE, NULL, NULL); + } + + probe_gps_features (task); +} + +static void +sgpss_test_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + if (!mm_base_modem_at_command_finish (self, res, NULL)) + priv->sgpss_support = FEATURE_NOT_SUPPORTED; + else { + /* ^SGPSS supported! */ + priv->sgpss_support = FEATURE_SUPPORTED; + + /* Flag ^SGPSC as unsupported, even if it may be supported, so that we + * only use one set of commands to enable/disable GPS. */ + priv->sgpsc_support = FEATURE_NOT_SUPPORTED; + + /* It may happen that the modem was started with GPS already enabled, or + * maybe ModemManager got rebooted and it was left enabled before. We'll + * make sure that it is disabled when we initialize the modem. */ + mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSS=0", 3, FALSE, NULL, NULL); + } + + probe_gps_features (task); +} + +static void +probe_gps_features (GTask *task) +{ + MMSharedCinterion *self; + MMModemLocationSource sources; + Private *priv; + + self = MM_SHARED_CINTERION (g_task_get_source_object (task)); + priv = get_private (self); + + /* Need to check if SGPSS supported... */ + if (priv->sgpss_support == FEATURE_SUPPORT_UNKNOWN) { + mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSS=?", 3, TRUE, (GAsyncReadyCallback) sgpss_test_ready, task); + return; + } + + /* Need to check if SGPSC supported... */ + if (priv->sgpsc_support == FEATURE_SUPPORT_UNKNOWN) { + mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=?", 3, TRUE, (GAsyncReadyCallback) sgpsc_test_ready, task); + return; + } + + /* All GPS features probed */ + + /* Recover parent sources */ + sources = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + if (priv->sgpss_support == FEATURE_SUPPORTED || priv->sgpsc_support == FEATURE_SUPPORTED) { + mm_obj_dbg (self, "GPS commands supported: GPS capabilities enabled"); + + /* We only flag as supported by this implementation those sources not already + * supported by the parent implementation */ + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) + priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA; + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW)) + priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW; + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) + priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED; + + sources |= priv->supported_sources; + + /* Add handler for the NMEA traces in the GPS data port */ + mm_port_serial_gps_add_trace_handler (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)), + (MMPortSerialGpsTraceFn)trace_received, + self, + NULL); + } else + mm_obj_dbg (self, "no GPS command supported: no GPS capabilities"); + + g_task_return_int (task, (gssize) sources); + g_object_unref (task); +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Now our own check. If we don't have any GPS port, we're done */ + if (!mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) { + mm_obj_dbg (self, "no GPS data port found: no GPS capabilities"); + g_task_return_int (task, sources); + g_object_unref (task); + return; + } + + /* Cache sources supported by the parent */ + g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL); + + /* Probe all GPS features */ + probe_gps_features (task); +} + +void +mm_shared_cinterion_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + priv = get_private (MM_SHARED_CINTERION (self)); + task = g_task_new (self, NULL, callback, user_data); + + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->load_capabilities); + g_assert (priv->iface_modem_location_parent->load_capabilities_finish); + + priv->iface_modem_location_parent->load_capabilities (self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + task); +} + +/*****************************************************************************/ +/* Disable location gathering (Location interface) */ + +typedef enum { + DISABLE_LOCATION_GATHERING_GPS_STEP_FIRST, + DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSS, + DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE, + DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA, + DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT, + DISABLE_LOCATION_GATHERING_GPS_STEP_LAST, +} DisableLocationGatheringGpsStep; + +typedef struct { + MMModemLocationSource source; + DisableLocationGatheringGpsStep gps_step; + GError *sgpss_error; + GError *sgpsc_error; +} DisableLocationGatheringContext; + +static void +disable_location_gathering_context_free (DisableLocationGatheringContext *ctx) +{ + if (ctx->sgpss_error) + g_error_free (ctx->sgpss_error); + if (ctx->sgpsc_error) + g_error_free (ctx->sgpsc_error); + g_slice_free (DisableLocationGatheringContext, ctx); +} + +gboolean +mm_shared_cinterion_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void disable_location_gathering_context_gps_step (GTask *task); + +static void +disable_sgpsc_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + DisableLocationGatheringContext *ctx; + GError *error = NULL; + + ctx = (DisableLocationGatheringContext *) g_task_get_task_data (task); + + /* Store error, if not one available already, and continue */ + if (!mm_base_modem_at_command_finish (self, res, &error)) { + if (!ctx->sgpsc_error) + ctx->sgpsc_error = error; + else + g_error_free (error); + } + + ctx->gps_step++; + disable_location_gathering_context_gps_step (task); +} + +static void +disable_sgpss_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + DisableLocationGatheringContext *ctx; + + ctx = (DisableLocationGatheringContext *) g_task_get_task_data (task); + + /* Store error, if any, and continue */ + g_assert (!ctx->sgpss_error); + mm_base_modem_at_command_finish (self, res, &ctx->sgpss_error); + + ctx->gps_step++; + disable_location_gathering_context_gps_step (task); +} + +static void +disable_location_gathering_context_gps_step (GTask *task) +{ + DisableLocationGatheringContext *ctx; + MMSharedCinterion *self; + Private *priv; + + self = MM_SHARED_CINTERION (g_task_get_source_object (task)); + priv = get_private (self); + ctx = (DisableLocationGatheringContext *) g_task_get_task_data (task); + + /* Only one of both supported */ + g_assert ((priv->sgpss_support == FEATURE_SUPPORTED) || (priv->sgpsc_support == FEATURE_SUPPORTED)); + g_assert (!((priv->sgpss_support == FEATURE_SUPPORTED) && (priv->sgpsc_support == FEATURE_SUPPORTED))); + + switch (ctx->gps_step) { + case DISABLE_LOCATION_GATHERING_GPS_STEP_FIRST: + ctx->gps_step++; + /* fall through */ + + case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSS: + if (priv->sgpss_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SGPSS=0", + 3, FALSE, (GAsyncReadyCallback) disable_sgpss_ready, task); + return; + } + ctx->gps_step++; + /* fall through */ + + case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE: + if (priv->sgpsc_support == FEATURE_SUPPORTED) { + /* Engine off */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SGPSC=\"Engine\",\"0\"", + 3, FALSE, (GAsyncReadyCallback) disable_sgpsc_ready, task); + return; + } + ctx->gps_step++; + /* fall through */ + + case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA: + if (priv->sgpsc_support == FEATURE_SUPPORTED) { + /* Antenna off */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SGPSC=\"Power/Antenna\",\"off\"", + 3, FALSE, (GAsyncReadyCallback) disable_sgpsc_ready, task); + return; + } + ctx->gps_step++; + /* fall through */ + + case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT: + if (priv->sgpsc_support == FEATURE_SUPPORTED) { + /* NMEA output off */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SGPSC=\"NMEA/Output\",\"off\"", + 3, FALSE, (GAsyncReadyCallback) disable_sgpsc_ready, task); + return; + } + ctx->gps_step++; + /* fall through */ + + case DISABLE_LOCATION_GATHERING_GPS_STEP_LAST: + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMPortSerialGps *gps_port; + + /* Even if we get an error here, we try to close the GPS port */ + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + if (ctx->sgpss_error) { + g_task_return_error (task, ctx->sgpss_error); + g_clear_error (&ctx->sgpss_error); + } else if (ctx->sgpsc_error) { + g_task_return_error (task, ctx->sgpsc_error); + g_clear_error (&ctx->sgpsc_error); + } else { + priv->enabled_sources &= ~ctx->source; + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +parent_disable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + g_assert (priv->iface_modem_location_parent); + if (!priv->iface_modem_location_parent->disable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_cinterion_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DisableLocationGatheringContext *ctx; + MMModemLocationSource enabled_sources; + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_CINTERION (self)); + g_assert (priv->iface_modem_location_parent); + + /* Only consider request if it applies to one of the sources we are + * supporting, otherwise run parent disable */ + if (!(priv->supported_sources & source)) { + /* If disabling implemented by the parent, run it. */ + if (priv->iface_modem_location_parent->disable_location_gathering && + priv->iface_modem_location_parent->disable_location_gathering_finish) { + priv->iface_modem_location_parent->disable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_disable_location_gathering_ready, + task); + return; + } + /* Otherwise, we're done */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* We only expect GPS sources here */ + g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)); + + /* Flag as disabled to see how many others we would have left enabled */ + enabled_sources = priv->enabled_sources; + enabled_sources &= ~source; + + /* If there are still GPS-related sources enabled, do nothing else */ + if (enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + priv->enabled_sources &= ~source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Stop GPS engine if all GPS-related sources are disabled */ + ctx = g_slice_new0 (DisableLocationGatheringContext); + ctx->source = source; + ctx->gps_step = DISABLE_LOCATION_GATHERING_GPS_STEP_FIRST; + g_task_set_task_data (task, ctx, (GDestroyNotify) disable_location_gathering_context_free); + disable_location_gathering_context_gps_step (task); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +/* We will retry the SGPSC command that enables the Engine */ +#define MAX_SGPSC_ENGINE_RETRIES 3 + +/* Cinterion asks for 100ms some time between GPS commands, but we'll give up + * to 2000ms before setting the Engine configuration as 100ms didn't seem always + * enough (we would get +CME ERROR: 767 errors reported). */ +#define GPS_COMMAND_TIMEOUT_DEFAULT_MS 100 +#define GPS_COMMAND_TIMEOUT_ENGINE_MS 2000 + +typedef enum { + ENABLE_LOCATION_GATHERING_GPS_STEP_FIRST, + ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSS, + ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT, + ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA, + ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE, + ENABLE_LOCATION_GATHERING_GPS_STEP_LAST, +} EnableLocationGatheringGpsStep; + +typedef struct { + MMModemLocationSource source; + EnableLocationGatheringGpsStep gps_step; + guint sgpsc_engine_retries; +} EnableLocationGatheringContext; + +static void +enable_location_gathering_context_free (EnableLocationGatheringContext *ctx) +{ + g_slice_free (EnableLocationGatheringContext, ctx); +} + +gboolean +mm_shared_cinterion_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void enable_location_gathering_context_gps_step (GTask *task); + +static gboolean +enable_location_gathering_context_gps_step_schedule_cb (GTask *task) +{ + /* Run the scheduled step */ + enable_location_gathering_context_gps_step (task); + return G_SOURCE_REMOVE; +} + +static void +enable_sgpsc_or_sgpss_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + EnableLocationGatheringContext *ctx; + GError *error = NULL; + + ctx = (EnableLocationGatheringContext *) g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + /* The GPS setup may sometimes report "+CME ERROR 767" when enabling the + * Engine; so we'll run some retries of the same command ourselves. */ + if (ctx->gps_step == ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE) { + ctx->sgpsc_engine_retries++; + mm_obj_dbg (self, "GPS engine setup failed (%u/%u)", ctx->sgpsc_engine_retries, MAX_SGPSC_ENGINE_RETRIES); + if (ctx->sgpsc_engine_retries < MAX_SGPSC_ENGINE_RETRIES) { + g_clear_error (&error); + goto schedule; + } + } + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go on to next step */ + ctx->gps_step++; + +schedule: + g_timeout_add (ctx->gps_step == ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE ? GPS_COMMAND_TIMEOUT_ENGINE_MS : GPS_COMMAND_TIMEOUT_DEFAULT_MS, + (GSourceFunc) enable_location_gathering_context_gps_step_schedule_cb, task); +} + +static void +enable_location_gathering_context_gps_step (GTask *task) +{ + EnableLocationGatheringContext *ctx; + MMSharedCinterion *self; + Private *priv; + + self = MM_SHARED_CINTERION (g_task_get_source_object (task)); + priv = get_private (self); + ctx = (EnableLocationGatheringContext *) g_task_get_task_data (task); + + /* Only one of both supported */ + g_assert ((priv->sgpss_support == FEATURE_SUPPORTED) || (priv->sgpsc_support == FEATURE_SUPPORTED)); + g_assert (!((priv->sgpss_support == FEATURE_SUPPORTED) && (priv->sgpsc_support == FEATURE_SUPPORTED))); + + switch (ctx->gps_step) { + case ENABLE_LOCATION_GATHERING_GPS_STEP_FIRST: + ctx->gps_step++; + /* fall through */ + + case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSS: + if (priv->sgpss_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SGPSS=4", + 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task); + return; + } + ctx->gps_step++; + /* fall through */ + + case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT: + if (priv->sgpsc_support == FEATURE_SUPPORTED) { + /* NMEA output off */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SGPSC=\"NMEA/Output\",\"on\"", + 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task); + return; + } + ctx->gps_step++; + /* fall through */ + + case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA: + if (priv->sgpsc_support == FEATURE_SUPPORTED) { + /* Antenna off */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SGPSC=\"Power/Antenna\",\"on\"", + 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task); + return; + } + ctx->gps_step++; + /* fall through */ + + case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE: + if (priv->sgpsc_support == FEATURE_SUPPORTED) { + /* Engine off */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SGPSC=\"Engine\",\"1\"", + 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task); + return; + } + ctx->gps_step++; + /* fall through */ + + case ENABLE_LOCATION_GATHERING_GPS_STEP_LAST: + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMPortSerialGps *gps_port; + GError *error = NULL; + + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (!gps_port || !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't open raw GPS serial port"); + g_object_unref (task); + return; + } + } + + /* Success */ + priv->enabled_sources |= ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + g_assert (priv->iface_modem_location_parent); + if (!priv->iface_modem_location_parent->enable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_cinterion_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + EnableLocationGatheringContext *ctx; + + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_CINTERION (self)); + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->enable_location_gathering); + g_assert (priv->iface_modem_location_parent->enable_location_gathering_finish); + + /* Only consider request if it applies to one of the sources we are + * supporting, otherwise run parent enable */ + if (!(priv->supported_sources & source)) { + priv->iface_modem_location_parent->enable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); + return; + } + + /* We only expect GPS sources here */ + g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)); + + /* If GPS already started, store new flag and we're done */ + if (priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + ctx = g_slice_new0 (EnableLocationGatheringContext); + ctx->source = source; + ctx->gps_step = ENABLE_LOCATION_GATHERING_GPS_STEP_FIRST; + g_task_set_task_data (task, ctx, (GDestroyNotify) enable_location_gathering_context_free); + + enable_location_gathering_context_gps_step (task); +} + +/*****************************************************************************/ + +MMBaseCall * +mm_shared_cinterion_create_call (MMIfaceModemVoice *self, + MMCallDirection direction, + const gchar *number) +{ + Private *priv; + + /* If ^SLCC is supported create a cinterion call object */ + priv = get_private (MM_SHARED_CINTERION (self)); + if (priv->slcc_support == FEATURE_SUPPORTED) { + mm_obj_dbg (self, "created new call with ^SLCC support"); + return mm_base_call_new (MM_BASE_MODEM (self), + direction, + number, + /* When SLCC is supported we have support for detailed + * call list events via call list report URCs */ + TRUE, /* incoming timeout not required */ + TRUE, /* dialing->ringing supported */ + TRUE); /* ringing->active supported */ + } + + /* otherwise, run parent's generic base call logic */ + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->create_call); + return priv->iface_modem_voice_parent->create_call (self, direction, number); +} + +/*****************************************************************************/ +/* Common enable/disable voice unsolicited events */ + +typedef struct { + gboolean enable; + MMPortSerialAt *primary; + MMPortSerialAt *secondary; + gchar *slcc_command; + gboolean slcc_primary_done; + gboolean slcc_secondary_done; +} VoiceUnsolicitedEventsContext; + +static void +voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx) +{ + g_clear_object (&ctx->secondary); + g_clear_object (&ctx->primary); + g_free (ctx->slcc_command); + g_slice_free (VoiceUnsolicitedEventsContext, ctx); +} + +static gboolean +common_voice_enable_disable_unsolicited_events_finish (MMSharedCinterion *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void run_voice_enable_disable_unsolicited_events (GTask *task); + +static void +slcc_command_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + VoiceUnsolicitedEventsContext *ctx; + g_autoptr(GError) error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_full_finish (self, res, &error)) + mm_obj_dbg (self, "couldn't %s ^SLCC reporting: %s", + ctx->enable ? "enable" : "disable", + error->message); + + /* Continue on next port */ + run_voice_enable_disable_unsolicited_events (task); +} + +static void +run_voice_enable_disable_unsolicited_events (GTask *task) +{ + MMSharedCinterion *self; + Private *priv; + VoiceUnsolicitedEventsContext *ctx; + MMPortSerialAt *port = NULL; + + self = MM_SHARED_CINTERION (g_task_get_source_object (task)); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + /* If not ^SLCC supported, we're done */ + if (priv->slcc_support == FEATURE_NOT_SUPPORTED) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (!ctx->slcc_primary_done && ctx->primary) { + mm_obj_dbg (self, "%s ^SLCC extended list of current calls reporting in primary port...", + ctx->enable ? "enabling" : "disabling"); + ctx->slcc_primary_done = TRUE; + port = ctx->primary; + } else if (!ctx->slcc_secondary_done && ctx->secondary) { + mm_obj_dbg (self, "%s ^SLCC extended list of current calls reporting in secondary port...", + ctx->enable ? "enabling" : "disabling"); + ctx->slcc_secondary_done = TRUE; + port = ctx->secondary; + } + + if (port) { + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + port, + ctx->slcc_command, + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)slcc_command_ready, + task); + return; + } + + /* Fully done now */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +common_voice_enable_disable_unsolicited_events (MMSharedCinterion *self, + gboolean enable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + VoiceUnsolicitedEventsContext *ctx; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_slice_new0 (VoiceUnsolicitedEventsContext); + ctx->enable = enable; + if (enable) + ctx->slcc_command = g_strdup ("^SLCC=1"); + else + ctx->slcc_command = g_strdup ("^SLCC=0"); + ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); + ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); + g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free); + + run_voice_enable_disable_unsolicited_events (task); +} + +/*****************************************************************************/ +/* Disable unsolicited events (Voice interface) */ + +gboolean +mm_shared_cinterion_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + if (!priv->iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "couldn't disable parent voice unsolicited events: %s", error->message); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +voice_disable_unsolicited_events_ready (MMSharedCinterion *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + g_autoptr(GError) error = NULL; + + if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "couldn't disable Cinterion-specific voice unsolicited events: %s", error->message); + + priv = get_private (MM_SHARED_CINTERION (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events_finish); + + /* Chain up parent's disable */ + priv->iface_modem_voice_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_VOICE (self), + (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready, + task); +} + +void +mm_shared_cinterion_voice_disable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* our own disabling first */ + common_voice_enable_disable_unsolicited_events (MM_SHARED_CINTERION (self), + FALSE, + (GAsyncReadyCallback) voice_disable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Enable unsolicited events (Voice interface) */ + +gboolean +mm_shared_cinterion_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +voice_enable_unsolicited_events_ready (MMSharedCinterion *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "couldn't enable Cinterion-specific voice unsolicited events: %s", error->message); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + if (!priv->iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "couldn't enable parent voice unsolicited events: %s", error->message); + + /* our own enabling next */ + common_voice_enable_disable_unsolicited_events (MM_SHARED_CINTERION (self), + TRUE, + (GAsyncReadyCallback) voice_enable_unsolicited_events_ready, + task); +} + +void +mm_shared_cinterion_voice_enable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_CINTERION (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events_finish); + + /* chain up parent's enable first */ + priv->iface_modem_voice_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Common setup/cleanup voice unsolicited events */ + +static void +slcc_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMSharedCinterion *self) +{ + g_autofree gchar *full = NULL; + g_autoptr(GError) error = NULL; + GList *call_info_list = NULL; + + full = g_match_info_fetch (match_info, 0); + if (!mm_cinterion_parse_slcc_list (full, self, &call_info_list, &error)) + mm_obj_warn (self, "couldn't parse ^SLCC list: %s", error->message); + else + mm_iface_modem_voice_report_all_calls (MM_IFACE_MODEM_VOICE (self), call_info_list); + mm_cinterion_call_info_list_free (call_info_list); +} + +static void +common_voice_setup_cleanup_unsolicited_events (MMSharedCinterion *self, + gboolean enable) +{ + Private *priv; + MMPortSerialAt *ports[2]; + guint i; + + priv = get_private (MM_SHARED_CINTERION (self)); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->slcc_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)slcc_received : NULL, + enable ? self : NULL, + NULL); + } +} + +/*****************************************************************************/ +/* Cleanup unsolicited events (Voice interface) */ + +gboolean +mm_shared_cinterion_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + if (!priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "couldn't cleanup parent voice unsolicited events: %s", error->message); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_cinterion_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_CINTERION (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish); + + /* our own cleanup first */ + common_voice_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), FALSE); + + /* Chain up parent's cleanup */ + priv->iface_modem_voice_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Setup unsolicited events (Voice interface) */ + +gboolean +mm_shared_cinterion_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + if (!priv->iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "Couldn't setup parent voice unsolicited events: %s", error->message); + + /* our own setup next */ + common_voice_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), TRUE); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_cinterion_voice_setup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_CINTERION (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events_finish); + + /* chain up parent's setup first */ + priv->iface_modem_voice_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Check if Voice supported (Voice interface) */ + +gboolean +mm_shared_cinterion_voice_check_support_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +slcc_format_check_ready (MMBroadbandModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + /* ^SLCC supported unless we got any error response */ + priv->slcc_support = (!!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ? + FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED); + + /* If ^SLCC supported we won't need polling in the parent */ + g_object_set (self, + MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, (priv->slcc_support == FEATURE_SUPPORTED), + NULL); + + /* ^SLCC command is supported; assume we have full voice capabilities */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_voice_check_support_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + GError *error = NULL; + + priv = get_private (MM_SHARED_CINTERION (self)); + if (!priv->iface_modem_voice_parent->check_support_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* voice is supported, check if ^SLCC is available */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SLCC=?", + 3, + /* Do NOT cache as the reply may be different if PIN locked + * or unlocked. E.g. we may not support ^SLCC for emergency + * voice calls. */ + FALSE, + (GAsyncReadyCallback) slcc_format_check_ready, + task); +} + +void +mm_shared_cinterion_voice_check_support (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_CINTERION (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->check_support); + g_assert (priv->iface_modem_voice_parent->check_support_finish); + + /* chain up parent's setup first */ + priv->iface_modem_voice_parent->check_support ( + self, + (GAsyncReadyCallback)parent_voice_check_support_ready, + task); +} + +/*****************************************************************************/ +/* Common setup/cleanup time unsolicited events */ + +static void +ctzu_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMSharedCinterion *self) +{ + g_autofree gchar *iso8601 = NULL; + g_autoptr(MMNetworkTimezone) tz = NULL; + g_autoptr(GError) error = NULL; + + if (!mm_cinterion_parse_ctzu_urc (match_info, &iso8601, &tz, &error)) { + mm_obj_dbg (self, "couldn't process +CTZU URC: %s", error->message); + return; + } + + mm_obj_dbg (self, "+CTZU URC received: %s", iso8601); + mm_iface_modem_time_update_network_time (MM_IFACE_MODEM_TIME (self), iso8601); + mm_iface_modem_time_update_network_timezone (MM_IFACE_MODEM_TIME (self), tz); +} + +static void +common_time_setup_cleanup_unsolicited_events (MMSharedCinterion *self, + gboolean enable) +{ + Private *priv; + MMPortSerialAt *ports[2]; + guint i; + + priv = get_private (MM_SHARED_CINTERION (self)); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + mm_obj_dbg (self, "%s up time unsolicited events...", + enable ? "setting" : "cleaning"); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->ctzu_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)ctzu_received : NULL, + enable ? self : NULL, + NULL); + } +} + +/*****************************************************************************/ +/* Cleanup unsolicited events (Time interface) */ + +gboolean +mm_shared_cinterion_time_cleanup_unsolicited_events_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_time_cleanup_unsolicited_events_ready (MMIfaceModemTime *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + if (!priv->iface_modem_time_parent->cleanup_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "couldn't cleanup parent time unsolicited events: %s", error->message); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_cinterion_time_cleanup_unsolicited_events (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_CINTERION (self)); + g_assert (priv->iface_modem_time_parent); + + /* our own cleanup first */ + common_time_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), FALSE); + + if (priv->iface_modem_time_parent->cleanup_unsolicited_events && + priv->iface_modem_time_parent->cleanup_unsolicited_events_finish) { + /* Chain up parent's cleanup */ + priv->iface_modem_time_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_time_cleanup_unsolicited_events_ready, + task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Setup unsolicited events (Time interface) */ + +gboolean +mm_shared_cinterion_time_setup_unsolicited_events_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +own_time_setup_unsolicited_events (GTask *task) +{ + MMSharedCinterion *self; + + self = g_task_get_source_object (task); + + /* our own setup next */ + common_time_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), TRUE); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_time_setup_unsolicited_events_ready (MMIfaceModemTime *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_CINTERION (self)); + + if (!priv->iface_modem_time_parent->cleanup_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "Couldn't cleanup parent time unsolicited events: %s", error->message); + + own_time_setup_unsolicited_events (task); +} + +void +mm_shared_cinterion_time_setup_unsolicited_events (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_CINTERION (self)); + g_assert (priv->iface_modem_time_parent); + + if (priv->iface_modem_time_parent->setup_unsolicited_events && + priv->iface_modem_time_parent->setup_unsolicited_events_finish) { + /* chain up parent's setup first */ + priv->iface_modem_time_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_time_setup_unsolicited_events_ready, + task); + return; + } + + own_time_setup_unsolicited_events (task); +} + +/*****************************************************************************/ + +static void +shared_cinterion_init (gpointer g_iface) +{ +} + +GType +mm_shared_cinterion_get_type (void) +{ + static GType shared_cinterion_type = 0; + + if (!G_UNLIKELY (shared_cinterion_type)) { + static const GTypeInfo info = { + sizeof (MMSharedCinterion), /* class_size */ + shared_cinterion_init, /* base_init */ + NULL, /* base_finalize */ + }; + + shared_cinterion_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedCinterion", &info, 0); + g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM); + g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM_VOICE); + g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM_TIME); + g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM_LOCATION); + } + + return shared_cinterion_type; +} diff --git a/src/plugins/cinterion/mm-shared-cinterion.h b/src/plugins/cinterion/mm-shared-cinterion.h new file mode 100644 index 00000000..eb6beac8 --- /dev/null +++ b/src/plugins/cinterion/mm-shared-cinterion.h @@ -0,0 +1,153 @@ +/* -*- 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) 2014 Ammonit Measurement GmbH + * Copyright (C) 2014 - 2018 Aleksander Morgado <aleksander@aleksander.es> + * Copyright (C) 2019 Purism SPC + */ + +#ifndef MM_SHARED_CINTERION_H +#define MM_SHARED_CINTERION_H + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" +#include "mm-iface-modem-time.h" + +#define MM_TYPE_SHARED_CINTERION (mm_shared_cinterion_get_type ()) +#define MM_SHARED_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_CINTERION, MMSharedCinterion)) +#define MM_IS_SHARED_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_CINTERION)) +#define MM_SHARED_CINTERION_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_CINTERION, MMSharedCinterion)) + +typedef struct _MMSharedCinterion MMSharedCinterion; + +struct _MMSharedCinterion { + GTypeInterface g_iface; + + /* Peek modem interface of the parent class of the object */ + MMIfaceModem * (* peek_parent_interface) (MMSharedCinterion *self); + + /* Peek location interface of the parent class of the object */ + MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedCinterion *self); + + /* Peek voice interface of the parent class of the object */ + MMIfaceModemVoice * (* peek_parent_voice_interface) (MMSharedCinterion *self); + + /* Peek time interface of the parent class of the object */ + MMIfaceModemTime * (* peek_parent_time_interface) (MMSharedCinterion *self); +}; + +GType mm_shared_cinterion_get_type (void); + +/*****************************************************************************/ +/* Modem interface */ + +void mm_shared_cinterion_modem_reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_cinterion_modem_reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +/*****************************************************************************/ +/* Location interface */ + +void mm_shared_cinterion_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMModemLocationSource mm_shared_cinterion_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); + +void mm_shared_cinterion_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_cinterion_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); + +void mm_shared_cinterion_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_cinterion_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); + +/*****************************************************************************/ +/* Voice interface */ + +MMBaseCall *mm_shared_cinterion_create_call (MMIfaceModemVoice *self, + MMCallDirection direction, + const gchar *number); + +void mm_shared_cinterion_voice_check_support (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_cinterion_voice_check_support_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_cinterion_voice_setup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_cinterion_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_cinterion_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_cinterion_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_cinterion_voice_enable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_cinterion_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_cinterion_voice_disable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_cinterion_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +/*****************************************************************************/ +/* Time interface */ + +void mm_shared_cinterion_time_setup_unsolicited_events (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_cinterion_time_setup_unsolicited_events_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error); + +void mm_shared_cinterion_time_cleanup_unsolicited_events (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_cinterion_time_cleanup_unsolicited_events_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error); + +#endif /* MM_SHARED_CINTERION_H */ diff --git a/src/plugins/cinterion/tests/test-modem-helpers-cinterion.c b/src/plugins/cinterion/tests/test-modem-helpers-cinterion.c new file mode 100644 index 00000000..d4816199 --- /dev/null +++ b/src/plugins/cinterion/tests/test-modem-helpers-cinterion.c @@ -0,0 +1,1967 @@ +/* -*- 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) 2014 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> +#include <math.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-cinterion.h" + +#define g_assert_cmpfloat_tolerance(val1, val2, tolerance) \ + g_assert_cmpfloat (fabs (val1 - val2), <, tolerance) + +/*****************************************************************************/ +/* Test ^SCFG test responses */ + +static void +common_test_scfg (const gchar *response, + GArray *expected_bands, + MMModemCharset charset, + MMCinterionModemFamily modem_family) +{ + GArray *bands = NULL; + gchar *expected_bands_str; + gchar *bands_str; + GError *error = NULL; + gboolean res; + MMCinterionRadioBandFormat format; + + res = mm_cinterion_parse_scfg_test (response, + modem_family, + charset, + &bands, + &format, + &error); + g_assert_no_error (error); + g_assert (res == TRUE); + g_assert (bands != NULL); + + mm_common_bands_garray_sort (bands); + mm_common_bands_garray_sort (expected_bands); + + expected_bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)expected_bands->data, + expected_bands->len); + bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)bands->data, + bands->len); + + /* Instead of comparing the array one by one, compare the strings built from the mask + * (we get a nicer error if it fails) */ + g_assert_cmpstr (bands_str, ==, expected_bands_str); + + g_free (bands_str); + g_free (expected_bands_str); + g_array_unref (bands); +} + +static void +test_scfg (void) +{ + GArray *expected_bands; + MMModemBand single; + const gchar *response = + "^SCFG: \"Audio/Loop\",(\"0\",\"1\")\r\n" + "^SCFG: \"Call/ECC\",(\"0\"-\"255\")\r\n" + "^SCFG: \"Call/Speech/Codec\",(\"0\",\"1\")\r\n" + "^SCFG: \"GPRS/Auth\",(\"0\",\"1\",\"2\")\r\n" + "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n" + "^SCFG: \"GPRS/MaxDataRate/HSDPA\",(\"0\",\"1\")\r\n" + "^SCFG: \"GPRS/MaxDataRate/HSUPA\",(\"0\",\"1\")\r\n" + "^SCFG: \"Ident/Manufacturer\",(25)\r\n" + "^SCFG: \"Ident/Product\",(25)\r\n" + "^SCFG: \"MEopMode/Airplane\",(\"off\",\"on\")\r\n" + "^SCFG: \"MEopMode/CregRoam\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEopMode/CFUN\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEopMode/PowerMgmt/LCI\",(\"disabled\",\"enabled\")\r\n" + "^SCFG: \"MEopMode/PowerMgmt/VExt\",(\"high\",\"low\")\r\n" + "^SCFG: \"MEopMode/PwrSave\",(\"disabled\",\"enabled\"),(\"0-600\"),(\"1-36000\")\r\n" + "^SCFG: \"MEopMode/RingOnData\",(\"on\",\"off\")\r\n" + "^SCFG: \"MEopMode/RingUrcOnCall\",(\"on\",\"off\")\r\n" + "^SCFG: \"MEShutdown/OnIgnition\",(\"on\",\"off\")\r\n" + "^SCFG: \"Radio/Band\",(\"1-511\",\"0-1\")\r\n" + "^SCFG: \"Radio/NWSM\",(\"0\",\"1\",\"2\")\r\n" + "^SCFG: \"Radio/OutputPowerReduction\",(\"4\"-\"8\")\r\n" + "^SCFG: \"Serial/USB/DDD\",(\"0\",\"1\"),(\"0\"),(4),(4),(4),(63),(63),(4)\r\n" + "^SCFG: \"URC/DstIfc\",(\"mdm\",\"app\")\r\n" + "^SCFG: \"URC/Datamode/Ringline\",(\"off\",\"on\")\r\n" + "^SCFG: \"URC/Ringline\",(\"off\",\"local\",\"asc0\",\"wakeup\")\r\n" + "^SCFG: \"URC/Ringline/ActiveTime\",(\"0\",\"1\",\"2\",\"keep\")\r\n"; + + expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9); + single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_6, g_array_append_val (expected_bands, single); + + common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_UNKNOWN, MM_CINTERION_MODEM_FAMILY_DEFAULT); + + g_array_unref (expected_bands); +} + +static void +test_scfg_ehs5 (void) +{ + GArray *expected_bands; + MMModemBand single; + const gchar *response = + "^SCFG: \"Audio/Loop\",(\"0\",\"1\")\r\n" + "^SCFG: \"Call/ECC\",(\"0\"-\"255\")\r\n" + "^SCFG: \"Call/Ecall/AckTimeout\",(\"0-2147483646\")\r\n" + "^SCFG: \"Call/Ecall/Callback\",(\"0\",\"1\")\r\n" + "^SCFG: \"Call/Ecall/CallbackTimeout\",(\"0-2147483646\")\r\n" + "^SCFG: \"Call/Ecall/Msd\",(\"280\")\r\n" + "^SCFG: \"Call/Ecall/Pullmode\",(\"0\",\"1\")\r\n" + "^SCFG: \"Call/Ecall/SessionTimeout\",(\"0-2147483646\")\r\n" + "^SCFG: \"Call/Ecall/StartTimeout\",(\"0-2147483646\")\r\n" + "^SCFG: \"Call/Speech/Codec\",(\"0\",\"1\")\r\n" + "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n" + "^SCFG: \"Gpio/mode/ASC1\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/DAI\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/DCD0\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/DSR0\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/DTR0\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/FSR\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/PULSE\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/PWM\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/RING0\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/SPI\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/SYNC\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Ident/Manufacturer\",(25)\r\n" + "^SCFG: \"Ident/Product\",(25)\r\n" + "^SCFG: \"MEShutdown/Fso\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEShutdown/sVsup/threshold\",(\"-4\",\"-3\",\"-2\",\"-1\",\"0\",\"1\",\"2\",\"3\",\"4\"),(\"0\")\r\n" + "^SCFG: \"MEopMode/CFUN\",(\"0\",\"1\"),(\"1\",\"4\")\r\n" + "^SCFG: \"MEopMode/Dormancy\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEopMode/SoR\",(\"off\",\"on\")\r\n" + "^SCFG: \"Radio/Band\",(\"1\"-\"147\")\r\n" + "^SCFG: \"Radio/Mtpl\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"1\",\"8\"),(\"18\"-\"33\"),(\"18\"-\"27\")\r\n" + "^SCFG: \"Radio/Mtpl\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"16\",\"32\",\"64\",\"128\",\"256\"),(\"18\"-\"24\")\r\n" + "^SCFG: \"Radio/Mtpl\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"2\",\"4\"),(\"18\"-\"30\"),(\"18\"-\"26\")\r\n" + "^SCFG: \"Radio/OutputPowerReduction\",(\"0\",\"1\",\"2\",\"3\",\"4\")\r\n" + "^SCFG: \"Serial/Interface/Allocation\",(\"0\",\"1\",\"2\"),(\"0\",\"1\",\"2\")\r\n" + "^SCFG: \"Serial/USB/DDD\",(\"0\",\"1\"),(\"0\"),(4),(4),(4),(63),(63),(4)\r\n" + "^SCFG: \"Tcp/IRT\",(\"1\"-\"60\")\r\n" + "^SCFG: \"Tcp/MR\",(\"1\"-\"30\")\r\n" + "^SCFG: \"Tcp/OT\",(\"1\"-\"6000\")\r\n" + "^SCFG: \"Tcp/WithURCs\",(\"on\",\"off\")\r\n" + "^SCFG: \"Trace/Syslog/OTAP\",(\"0\",\"1\"),(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"usb3\",\"usb4\",\"usb5\",\"file\",\"udp\",\"system\"),(\"1\"-\"65535\"),(125),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n" + "^SCFG: \"URC/Ringline\",(\"off\",\"local\",\"asc0\")\r\n" + "^SCFG: \"URC/Ringline/ActiveTime\",(\"0\",\"1\",\"2\")\r\n" + "^SCFG: \"Userware/Autostart\",(\"0\",\"1\")\r\n" + "^SCFG: \"Userware/Autostart/Delay\",(\"0\"-\"10000\")\r\n" + "^SCFG: \"Userware/DebugInterface\",(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\",\"1\")\r\n" + "^SCFG: \"Userware/DebugMode\",(\"off\",\"on\")\r\n" + "^SCFG: \"Userware/Passwd\",(\"0\"-\"8\")\r\n" + "^SCFG: \"Userware/Stdout\",(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"usb3\",\"usb4\",\"usb5\",\"file\",\"udp\",\"system\"),(\"1\"-\"65535\"),(\"0\"-\"125\"),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n" + "^SCFG: \"Userware/Watchdog\",(\"0\",\"1\",\"2\")\r\n"; + + expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4); + single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single); + + common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_UNKNOWN, MM_CINTERION_MODEM_FAMILY_DEFAULT); + + g_array_unref (expected_bands); +} + +static void +test_scfg_pls62_gsm (void) +{ + GArray *expected_bands; + MMModemBand single; + const gchar *response = + "^SCFG: \"MEopMode/Prov/AutoSelect\",(\"off\",\"on\")\r\n" + "^SCFG: \"MEopMode/Prov/Cfg\",(\"fallback\",\"attus\")\r\n" + "^SCFG: \"Serial/Ifc\",(\"Current\",\"ASC0\",\"USB0\",\"USB1\",\"USB2\",\"MUX1\",\"MUX2\",\"MUX3\",\"0\"),(\"0\",\"3\"),(\"1200\",\"2400\",\"4800\",\"9600\",\"19200\",\"38400\",\"57600\",\"115200\",\"230400\",\"460800\",\"500000\",\"750000\",\"921600\"),(\"0)\r\n" + "^SCFG: \"RemoteWakeUp/Ports\",(\"current\",\"powerup\"),(\"asc0\",\"acm1\",\"acm2\",\"acm3\",\"rmnet0\",\"rmnet1\")\r\n" + "^SCFG: \"Gpio/mode/ASC1\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/DCD0\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/DSR0\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/DTR0\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/FSR\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/PULSE\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/PWM\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/HWAKEUP\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/RING0\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/SPI\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"Gpio/mode/SYNC\",(\"std\",\"gpio\",\"rsv\")\r\n" + "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n" + "^SCFG: \"Ident/Manufacturer\",(25)\r\n" + "^SCFG: \"Ident/Product\",(25)\r\n" + "^SCFG: \"MEopMode/SoR\",(\"off\",\"on\")\r\n" + "^SCFG: \"MEopMode/CregRoam\",(\"0\",\"1\")\r\n" + "^SCFG: \"MeOpMode/SRPOM\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEopMode/RingOnData\",(\"off\",\"on\")\r\n" + "^SCFG: \"MEShutdown/Fso\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEShutdown/sVsup/threshold\",(\"-4\",\"-3\",\"-2\",\"-1\",\"0\",\"1\",\"2\",\"3\",\"4\"),(\"0\")\r\n" + "^SCFG: \"Radio/Band/2G\",(\"0x00000004\"-\"0x00000074\")\r\n" + "^SCFG: \"Radio/Band/3G\",(\"0x00000001\"-\"0x0004019B\")\r\n" + "^SCFG: \"Radio/Band/4G\",(\"0x00000001\"-\"0x080E08DF\")\r\n" + "^SCFG: \"Radio/Mtpl/2G\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"0x00000004\",\"0x00000010\",\"0x00000020\",\"0x00000040\"),,(\"18\"-\"33\"),(\"18\"-\"27\")\r\n" + "^SCFG: \"Radio/Mtpl/3G\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"0x00000001\",\"0x00000002\",\"0x00000008\",\"0x00000010\",\"0x00000080\",\"0x00000100\",\"0x00040000\"),,(\"18\"-\"24\")\r\n" + "^SCFG: \"Radio/Mtpl/4G\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"0x00000001\",\"0x00000002\",\"0x00000004\",\"0x00000008\",\"0x00000010\",\"0x00000040\",\"0x00000080\",\"0x00000800\",\"0x00020000\",\"0x00040000\",\"0x00080000\",\"0x08000000\"),,(\"18)\r\n" + "^SCFG: \"Radio/OutputPowerReduction\",(\"0\",\"1\",\"2\",\"3\",\"4\")\r\n" + "^SCFG: \"Serial/Interface/Allocation\",(\"0\",\"1\"),(\"0\",\"1\")\r\n" + "^SCFG: \"Serial/USB/DDD\",(\"0\",\"1\"),(\"0\"),(4),(4),(4),(63),(63),(4)\r\n" + "^SCFG: \"Tcp/IRT\",(\"1\"-\"60\")\r\n" + "^SCFG: \"Tcp/MR\",(\"2\"-\"30\")\r\n" + "^SCFG: \"Tcp/OT\",(\"1\"-\"6000\")\r\n" + "^SCFG: \"Tcp/WithURCs\",(\"on\",\"off\")\r\n" + "^SCFG: \"Trace/Syslog/OTAP\",(\"0\",\"1\"),(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"file\",\"system\"),(\"1\"-\"65535\"),(125),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n" + "^SCFG: \"Urc/Ringline\",(\"off\",\"local\",\"asc0\",\"wakeup\")\r\n" + "^SCFG: \"Urc/Ringline/ActiveTime\",(\"0\",\"1\",\"2\")\r\n" + "^SCFG: \"Userware/Autostart\",(\"0\",\"1\")\r\n" + "^SCFG: \"Userware/Autostart/Delay\",(\"0\"-\"10000\")\r\n" + "^SCFG: \"Userware/DebugInterface\",(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\",\"1\")\r\n" + "^SCFG: \"Userware/DebugMode\",(\"off\",\"on\")\r\n" + "^SCFG: \"Userware/Passwd\",(\"0\"-\"8\")\r\n" + "^SCFG: \"Userware/Stdout\",(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"file\",\"system\"),(\"1\"-\"65535\"),(\"0\"-\"125\"),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n" + "^SCFG: \"Userware/Watchdog\",(\"0\",\"1\",\"2\")\r\n" + "^SCFG: \"MEopMode/ExpectDTR\",(\"current\",\"powerup\"),(\"asc0\",\"acm1\",\"acm2\",\"acm3\")\r\n"; + + expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23); + single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_4, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_2, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_4, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_12, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single); + + common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_GSM, MM_CINTERION_MODEM_FAMILY_IMT); + + g_array_unref (expected_bands); +} + +static void +test_scfg_pls62_ucs2 (void) +{ + GArray *expected_bands; + MMModemBand single; + const gchar *response = + "^SCFG: \"MEopMode/Prov/AutoSelect\",(\"006F00660066\",\"006F006E\")\r\n" + "^SCFG: \"MEopMode/Prov/Cfg\",(\"fallback\",\"attus\")\r\n" + "^SCFG: \"Serial/Ifc\",(\"00430075007200720065006E0074\",\"0041005300430030\",\"0055005300420030\",\"0055005300420031\",\"0055005300420032\",\"004D005500580031\",\"004D005500580032\",\"004D005500580033\",\"0030\"),(\"0030\",\"0033)\r\n" + "^SCFG: \"RemoteWakeUp/Ports\",(\"00630075007200720065006E0074\",\"0070006F00770065007200750070\"),(\"0061007300630030\",\"00610063006D0031\",\"00610063006D0032\",\"00610063006D0033\",\"0072006D006E006500740030\",\"0072006D0)\r\n" + "^SCFG: \"Gpio/mode/ASC1\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n" + "^SCFG: \"Gpio/mode/DCD0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n" + "^SCFG: \"Gpio/mode/DSR0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n" + "^SCFG: \"Gpio/mode/DTR0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n" + "^SCFG: \"Gpio/mode/FSR\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n" + "^SCFG: \"Gpio/mode/PULSE\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n" + "^SCFG: \"Gpio/mode/PWM\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n" + "^SCFG: \"Gpio/mode/HWAKEUP\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n" + "^SCFG: \"Gpio/mode/RING0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n" + "^SCFG: \"Gpio/mode/SPI\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n" + "^SCFG: \"Gpio/mode/SYNC\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n" + "^SCFG: \"GPRS/AutoAttach\",(\"00640069007300610062006C00650064\",\"0065006E00610062006C00650064\")\r\n" + "^SCFG: \"Ident/Manufacturer\",(25)\r\n" + "^SCFG: \"Ident/Product\",(25)\r\n" + "^SCFG: \"MEopMode/SoR\",(\"006F00660066\",\"006F006E\")\r\n" + "^SCFG: \"MEopMode/CregRoam\",(\"0030\",\"0031\")\r\n" + "^SCFG: \"MeOpMode/SRPOM\",(\"0030\",\"0031\")\r\n" + "^SCFG: \"MEopMode/RingOnData\",(\"006F00660066\",\"006F006E\")\r\n" + "^SCFG: \"MEShutdown/Fso\",(\"0030\",\"0031\")\r\n" + "^SCFG: \"MEShutdown/sVsup/threshold\",(\"002D0034\",\"002D0033\",\"002D0032\",\"002D0031\",\"0030\",\"0031\",\"0032\",\"0033\",\"0034\"),(\"0030\")\r\n" + "^SCFG: \"Radio/Band/2G\",(\"0030007800300030003000300030003000300034\"-\"0030007800300030003000300030003000370034\")\r\n" + "^SCFG: \"Radio/Band/3G\",(\"0030007800300030003000300030003000300031\"-\"0030007800300030003000340030003100390042\")\r\n" + "^SCFG: \"Radio/Band/4G\",(\"0030007800300030003000300030003000300031\"-\"0030007800300038003000450030003800440046\")\r\n" + "^SCFG: \"Radio/Mtpl/2G\",(\"00300022002D00220033\"),(\"00310022002D00220038\"),(\"00300078003000300030003000300030003000340022002C002200300078003000300030003000300030003100300022002C0022003000780030003000300030003)\r\n" + "^SCFG: \"Radio/Mtpl/3G\",(\"00300022002D00220033\"),(\"00310022002D00220038\"),(\"00300078003000300030003000300030003000310022002C002200300078003000300030003000300030003000320022002C0022003000780030003000300030003)\r\n" + "^SCFG: \"Radio/Mtpl/4G\",(\"00300022002D00220033\"),(\"00310022002D00220038\"),(\"00310022002D00220038\"),,(\"003100380022002D002200320033\")\r\n" + "^SCFG: \"Radio/OutputPowerReduction\",(\"0030\",\"0031\",\"0032\",\"0033\",\"0034\")\r\n" + "^SCFG: \"Serial/Interface/Allocation\",(\"0030\",\"0031\"),(\"0030\",\"0031\")\r\n" + "^SCFG: \"Serial/USB/DDD\",(\"0030\",\"0031\"),(\"0030\"),(4),(4),(4),(63),(63),(4)\r\n" + "^SCFG: \"Tcp/IRT\",(\"0031\"-\"00360030\")\r\n" + "^SCFG: \"Tcp/MR\",(\"0032\"-\"00330030\")\r\n" + "^SCFG: \"Tcp/OT\",(\"0031\"-\"0036003000300030\")\r\n" + "^SCFG: \"Tcp/WithURCs\",(\"006F006E\",\"006F00660066\")\r\n" + "^SCFG: \"Trace/Syslog/OTAP\",(\"0030\",\"0031\"),(\"006E0075006C006C\",\"0061007300630030\",\"0061007300630031\",\"007500730062\",\"0075007300620031\",\"0075007300620032\",\"00660069006C0065\",\"00730079007300740065006D\"),(\"003)\r\n" + "^SCFG: \"Urc/Ringline\",(\"006F00660066\",\"006C006F00630061006C\",\"0061007300630030\",\"00770061006B006500750070\")\r\n" + "^SCFG: \"Urc/Ringline/ActiveTime\",(\"0030\",\"0031\",\"0032\")\r\n" + "^SCFG: \"Userware/Autostart\",(\"0030\",\"0031\")\r\n" + "^SCFG: \"Userware/Autostart/Delay\",(\"00300022002D002200310030003000300030\")\r\n" + "^SCFG: \"Userware/DebugInterface\",(\"0030\"-\"003200350035\")|(\"0046004500380030003A003A\"-\"0046004500380030003A003A0046004600460046004600460046004600460046004600460046004600460046\"),(\"0030\"-\"003200350035\")|(\"004)\r\n" + "^SCFG: \"Userware/DebugMode\",(\"006F00660066\",\"006F006E\")\r\n" + "^SCFG: \"Userware/Passwd\",(\"0030\"-\"0038\")\r\n" + "^SCFG: \"Userware/Stdout\",(\"006E0075006C006C\",\"0061007300630030\",\"0061007300630031\",\"007500730062\",\"0075007300620031\",\"0075007300620032\",\"00660069006C0065\",\"00730079007300740065006D\"),(\"0031\"-\"00360035003500)\r\n" + "^SCFG: \"Userware/Watchdog\",(\"0030\",\"0031\",\"0032\")\r\n" + "^SCFG: \"MEopMode/ExpectDTR\",(\"00630075007200720065006E0074\",\"0070006F00770065007200750070\"),(\"0061007300630030\",\"00610063006D0031\",\"00610063006D0032\",\"00610063006D0033\")\r\n"; + + expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23); + single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_4, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_2, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_4, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_12, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single); + + common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_UCS2, MM_CINTERION_MODEM_FAMILY_IMT); + + g_array_unref (expected_bands); +} + +static void +test_scfg_alas5 (void) +{ + GArray *expected_bands; + MMModemBand single; + const gchar *response = + "^SCFG: \"Audio/Loop\",(\"0\",\"1\")\r\n" + "^SCFG: \"Audio/SvTone\",(\"0-2047\")\r\n" + "^SCFG: \"Call/Ecall/AckTimeout\",(\"0-60000\")\r\n" + "^SCFG: \"Call/Ecall/BlockSMSPP\",(\"0\",\"1\")\r\n" + "^SCFG: \"Call/Ecall/Callback\",(\"0\",\"1\")\r\n" + "^SCFG: \"Call/Ecall/CallbackTimeout\",(\"0-86400000\")\r\n" + "^SCFG: \"Call/Ecall/Force\",(\"0\",\"1\",\"2\")\r\n" + "^SCFG: \"Call/Ecall/Msd\",(280)\r\n" + "^SCFG: \"Call/Ecall/Pullmode\",(\"0\",\"1\")\r\n" + "^SCFG: \"Call/Ecall/SessionTimeout\",(\"0-300000\")\r\n" + "^SCFG: \"Call/Ecall/StartTimeout\",(\"0-600000\")\r\n" + "^SCFG: \"Call/ECC\",(\"0\"-\"255\")\r\n" + "^SCFG: \"Call/Speech/Codec\",(\"0\",\"2\")\r\n" + "^SCFG: \"GPRS/Auth\",(\"0\",\"1\",\"2\")\r\n" + "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n" + "^SCFG: \"GPRS/MTU/Mode\",(\"0-1\")\r\n" + "^SCFG: \"GPRS/MTU/Size\",(\"1280-4096\")\r\n" + "^SCFG: \"MEopMode/CFUN\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEopMode/CregRoam\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEopMode/Dormancy\",(\"0\",\"1\",\"9\")\r\n" + "^SCFG: \"MEopMode/DTM/Mode\",(\"0\",\"1\",\"2\")\r\n" + "^SCFG: \"MEopMode/ExpectDTR\",(\"current\",\"powerup\"),(\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\")\r\n" + "^SCFG: \"MEopMode/FGI/Split\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEopMode/IMS\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEopMode/NonBlock/Cops\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEopMode/PowerMgmt/LCI\",(\"disabled\",\"enabled\"),(\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n" + "^SCFG: \"MEopMode/Prov/AutoFallback\",(\"on\",\"off\")\r\n" + "^SCFG: \"MEopMode/Prov/AutoSelect\",(\"on\",\"off\")\r\n" + "^SCFG: \"MEopMode/Prov/Cfg\",(\"vdfde\",\"tmode\",\"clarobr\",\"telenorno\",\"telenorse\",\"vdfpt\",\"fallb3gpp*\",\"vdfww\",\"vdfes\",\"swisscomch\",\"eeuk\",\"orangero\",\"orangees\",\"tefde\",\"telenordk\",\"timit\",\"tn1de\",\"tefes\",\"tels)\r\n" + "^SCFG: \"MEopMode/PwrSave\",(\"disabled\",\"enabled\"),(\"0-36000\"),(\"0-36000\"),(\"CPU-A\",\"CPU-M\"),(\"powerup\",\"current\")\r\n" + "^SCFG: \"MEopMode/SRPOM\",(\"0\",\"1\")\r\n" + "^SCFG: \"MEopMode/USB/KeepData\",(\"current\",\"powerup\"),(\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\")\r\n" + "^SCFG: \"MEShutdown/OnIgnition\",(\"on\",\"off\")\r\n" + "^SCFG: \"MEShutdown/Timer\",(\"off\",\"0\"-\"525600\")\r\n" + "^SCFG: \"Misc/CId\",(290)\r\n" + "^SCFG: \"Radio/Band/2G\",(\"00000001-0000000f\"),,(\"0\",\"1\")\r\n" + "^SCFG: \"Radio/Band/3G\",(\"00000001-000400b5\"),,(\"0\",\"1\")\r\n" + "^SCFG: \"Radio/Band/4G\",(\"00000001-8a0e00d5\"),(\"00000002-000001e2\"),(\"0\",\"1\")\r\n" + "^SCFG: \"Radio/CNS\",(\"0\",\"1\")\r\n" + "^SCFG: \"Radio/Mtpl\",(\"0-1\"),(\"1-8\")\r\n" + "^SCFG: \"Radio/Mtpl/2G\",(\"2-3\"),(\"1-8\"),(\"00000001-0000000f\"),,(\"18-33\"),(\"18-27\")\r\n" + "^SCFG: \"Radio/Mtpl/3G\",(\"2-3\"),(\"1-8\"),(\"00000001-000000b5\"),,(\"18-24\")\r\n" + "^SCFG: \"Radio/Mtpl/4G\",(\"2-3\"),(\"1-8\"),(\"00000001-8a0e00d5\"),(\"00000002-000000e2\"),(\"18-24\")\r\n" + "^SCFG: \"Radio/OutputPowerReduction\",(\"4\"-\"8\")\r\n" + "^SCFG: \"RemoteWakeUp/Event/ASC\",(\"none\",\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n" + "^SCFG: \"RemoteWakeUp/Event/URC\",(\"none\",\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n" + "^SCFG: \"RemoteWakeUp/Event/USB\",(\"none\",\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n" + "^SCFG: \"RemoteWakeUp/Ports\",(\"current\",\"powerup\"),(\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\")\r\n" + "^SCFG: \"RemoteWakeUp/Pulse\",(\"1\"-\"100\")\r\n" + "^SCFG: \"Serial/USB/DDD\",(\"0-1\"),(\"0\"),(\"0001-ffff\"),(\"0000-ffff\"),(\"0000-ffff\"),(63),(63),(4)\r\n" + "^SCFG: \"SIM/CS\",(\"NOSIM\",\"SIM1\",\"SIM2\")\r\n" + "^SCFG: \"SMS/4GPREF\",(\"IMS\",\"CSPS\")\r\n" + "^SCFG: \"SMS/AutoAck\",(\"0\",\"1\")\r\n" + "^SCFG: \"SMS/RETRM\",(\"1-45\")\r\n" + "^SCFG: \"URC/Ringline\",(\"off\",\"local\",\"asc0\")\r\n" + "^SCFG: \"URC/Ringline/ActiveTime\",(\"2\",\"on\",\"off\")\r\n"; + + expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23); + single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_3, g_array_append_val (expected_bands, single); // + single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_6, g_array_append_val (expected_bands, single); // + single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_26, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_38, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_39, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_40, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_41, g_array_append_val (expected_bands, single); + + common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_GSM, MM_CINTERION_MODEM_FAMILY_DEFAULT); + + g_array_unref (expected_bands); +} + +/*****************************************************************************/ +/* Test ^SCFG responses */ + +static void +common_test_scfg_response (const gchar *response, + MMModemCharset charset, + GArray *expected_bands, + MMCinterionModemFamily modem_family, + MMCinterionRadioBandFormat rbf) +{ + GArray *bands = NULL; + gchar *expected_bands_str; + gchar *bands_str; + GError *error = NULL; + gboolean res; + + res = mm_cinterion_parse_scfg_response (response, modem_family, charset, &bands, rbf, &error); + g_assert_no_error (error); + g_assert (res == TRUE); + g_assert (bands != NULL); + + mm_common_bands_garray_sort (bands); + mm_common_bands_garray_sort (expected_bands); + + expected_bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)expected_bands->data, + expected_bands->len); + bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)bands->data, + bands->len); + + /* Instead of comparing the array one by one, compare the strings built from the mask + * (we get a nicer error if it fails) */ + g_assert_cmpstr (bands_str, ==, expected_bands_str); + + g_free (bands_str); + g_free (expected_bands_str); + g_array_unref (bands); +} + +static void +test_scfg_response_2g (void) +{ + GArray *expected_bands; + MMModemBand single; + const gchar *response = + "^SCFG: \"Radio/Band\",\"3\",\"3\"\r\n" + "\r\n"; + + expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9); + single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single); + + common_test_scfg_response (response, MM_MODEM_CHARSET_UNKNOWN, expected_bands, MM_CINTERION_MODEM_FAMILY_DEFAULT, MM_CINTERION_RADIO_BAND_FORMAT_SINGLE); + + g_array_unref (expected_bands); +} + +static void +test_scfg_response_3g (void) +{ + GArray *expected_bands; + MMModemBand single; + const gchar *response = + "^SCFG: \"Radio/Band\",127\r\n" + "\r\n"; + + expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9); + single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single); + + common_test_scfg_response (response, MM_MODEM_CHARSET_UNKNOWN, expected_bands, MM_CINTERION_MODEM_FAMILY_DEFAULT, MM_CINTERION_RADIO_BAND_FORMAT_SINGLE); + + g_array_unref (expected_bands); +} + +static void +test_scfg_response_pls62_gsm (void) +{ + GArray *expected_bands; + MMModemBand single; + const gchar *response = + "^SCFG: \"MEopMode/Prov/AutoSelect\",\"off\"\r\n" + "^SCFG: \"MEopMode/Prov/Cfg\",\"attus\"\r\n" + "^SCFG: \"Serial/Ifc\",\"0\"\r\n" + "^SCFG: \"RemoteWakeUp/Ports\",\"current\"\r\n" + "^SCFG: \"RemoteWakeUp/Ports\",\"powerup\"\r\n" + "^SCFG: \"Gpio/mode/ASC1\",\"gpio\"\r\n" + "^SCFG: \"Gpio/mode/DCD0\",\"gpio\"\r\n" + "^SCFG: \"Gpio/mode/DSR0\",\"gpio\"\r\n" + "^SCFG: \"Gpio/mode/DTR0\",\"gpio\"\r\n" + "^SCFG: \"Gpio/mode/FSR\",\"gpio\"\r\n" + "^SCFG: \"Gpio/mode/PULSE\",\"gpio\"\r\n" + "^SCFG: \"Gpio/mode/PWM\",\"gpio\"\r\n" + "^SCFG: \"Gpio/mode/HWAKEUP\",\"gpio\"\r\n" + "^SCFG: \"Gpio/mode/RING0\",\"gpio\"\r\n" + "^SCFG: \"Gpio/mode/SPI\",\"gpio\"\r\n" + "^SCFG: \"Gpio/mode/SYNC\",\"gpio\"\r\n" + "^SCFG: \"GPRS/AutoAttach\",\"enabled\"\r\n" + "^SCFG: \"Ident/Manufacturer\",\"Cinterion\"\r\n" + "^SCFG: \"Ident/Product\",\"PLS62-W\"\r\n" + "^SCFG: \"MEopMode/SoR\",\"off\"\r\n" + "^SCFG: \"MEopMode/CregRoam\",\"0\"\r\n" + "^SCFG: \"MeOpMode/SRPOM\",\"0\"\r\n" + "^SCFG: \"MEopMode/RingOnData\",\"off\"\r\n" + "^SCFG: \"MEShutdown/Fso\",\"0\"\r\n" + "^SCFG: \"MEShutdown/sVsup/threshold\",\"0\",\"0\"\r\n" + "^SCFG: \"Radio/Band/2G\",\"0x00000014\"\r\n" + "^SCFG: \"Radio/Band/3G\",\"0x00000182\"\r\n" + "^SCFG: \"Radio/Band/4G\",\"0x080E0000\"\r\n" + "^SCFG: \"Radio/Mtpl/2G\",\"0\"\r\n" + "^SCFG: \"Radio/Mtpl/3G\",\"0\"\r\n" + "^SCFG: \"Radio/Mtpl/4G\",\"0\"\r\n" + "^SCFG: \"Radio/OutputPowerReduction\",\"4\"\r\n" + "^SCFG: \"Serial/Interface/Allocation\",\"0\",\"0\"\r\n" + "^SCFG: \"Serial/USB/DDD\",\"0\",\"0\",\"0409\",\"1E2D\",\"005B\",\"Cinterion Wireless Modules\",\"PLSx\",\"\"\r\n" + "^SCFG: \"Tcp/IRT\",\"3\"\r\n" + "^SCFG: \"Tcp/MR\",\"10\"\r\n" + "^SCFG: \"Tcp/OT\",\"6000\"\r\n" + "^SCFG: \"Tcp/WithURCs\",\"on\"\r\n" + "^SCFG: \"Trace/Syslog/OTAP\",\"0\"\r\n" + "^SCFG: \"Urc/Ringline\",\"local\"\r\n" + "^SCFG: \"Urc/Ringline/ActiveTime\",\"2\"\r\n" + "^SCFG: \"Userware/Autostart\",\"0\"\r\n" + "^SCFG: \"Userware/Autostart/Delay\",\"0\"\r\n" + "^SCFG: \"Userware/DebugInterface\",\"0.0.0.0\",\"0.0.0.0\",\"0\"\r\n" + "^SCFG: \"Userware/DebugMode\",\"off\"\r\n" + "^SCFG: \"Userware/Passwd\",\r\n" + "^SCFG: \"Userware/Stdout\",\"null\",,,,\"off\"\r\n" + "^SCFG: \"Userware/Watchdog\",\"0\"\r\n" + "^SCFG: \"MEopMode/ExpectDTR\",\"current\"\r\n" + "^SCFG: \"MEopMode/ExpectDTR\",\"powerup\"\r\n"; + + expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9); + single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single); + + common_test_scfg_response (response, MM_MODEM_CHARSET_GSM, expected_bands, MM_CINTERION_MODEM_FAMILY_IMT, MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE); + + g_array_unref (expected_bands); +} + +static void +test_scfg_response_pls62_ucs2 (void) +{ + GArray *expected_bands; + MMModemBand single; + const gchar *response = + "^SCFG: \"MEopMode/Prov/AutoSelect\",\"006F00660066\"\r\n" + "^SCFG: \"MEopMode/Prov/Cfg\",\"00610074007400750073\"\r\n" + "^SCFG: \"Serial/Ifc\",\"0\"\r\n" + "^SCFG: \"RemoteWakeUp/Ports\",\"00630075007200720065006E0074\"\r\n" + "^SCFG: \"RemoteWakeUp/Ports\",\"0070006F00770065007200750070\"\r\n" + "^SCFG: \"Gpio/mode/ASC1\",\"006700700069006F\"\r\n" + "^SCFG: \"Gpio/mode/DCD0\",\"006700700069006F\"\r\n" + "^SCFG: \"Gpio/mode/DSR0\",\"006700700069006F\"\r\n" + "^SCFG: \"Gpio/mode/DTR0\",\"006700700069006F\"\r\n" + "^SCFG: \"Gpio/mode/FSR\",\"006700700069006F\"\r\n" + "^SCFG: \"Gpio/mode/PULSE\",\"006700700069006F\"\r\n" + "^SCFG: \"Gpio/mode/PWM\",\"006700700069006F\"\r\n" + "^SCFG: \"Gpio/mode/HWAKEUP\",\"006700700069006F\"\r\n" + "^SCFG: \"Gpio/mode/RING0\",\"006700700069006F\"\r\n" + "^SCFG: \"Gpio/mode/SPI\",\"006700700069006F\"\r\n" + "^SCFG: \"Gpio/mode/SYNC\",\"006700700069006F\"\r\n" + "^SCFG: \"GPRS/AutoAttach\",\"0065006E00610062006C00650064\"\r\n" + "^SCFG: \"Ident/Manufacturer\",\"Cinterion\"\r\n" + "^SCFG: \"Ident/Product\",\"PLS62-W\"\r\n" + "^SCFG: \"MEopMode/SoR\",\"006F00660066\"\r\n" + "^SCFG: \"MEopMode/CregRoam\",\"0030\"\r\n" + "^SCFG: \"MeOpMode/SRPOM\",\"0030\"\r\n" + "^SCFG: \"MEopMode/RingOnData\",\"006F00660066\"\r\n" + "^SCFG: \"MEShutdown/Fso\",\"0030\"\r\n" + "^SCFG: \"MEShutdown/sVsup/threshold\",\"0030\",\"0030\"\r\n" + "^SCFG: \"Radio/Band/2G\",\"0030007800300030003000300030003000310034\"\r\n" + "^SCFG: \"Radio/Band/3G\",\"0030007800300030003000300030003100380032\"\r\n" + "^SCFG: \"Radio/Band/4G\",\"0030007800300038003000450030003000300030\"\r\n" + "^SCFG: \"Radio/Mtpl/2G\",\"0030\"\r\n" + "^SCFG: \"Radio/Mtpl/3G\",\"0030\"\r\n" + "^SCFG: \"Radio/Mtpl/4G\",\"0030\"\r\n" + "^SCFG: \"Radio/OutputPowerReduction\",\"0034\"\r\n" + "^SCFG: \"Serial/Interface/Allocation\",\"0030\",\"0030\"\r\n" + "^SCFG: \"Serial/USB/DDD\",\"0030\",\"0030\",\"0030003400300039\",\"0031004500320044\",\"0030003000350042\",\"00430069006E0074006500720069006F006E00200057006900720065006C0065007300730020004D006F00640075006C00650073\",\"005\"\r\n" + "^SCFG: \"Tcp/IRT\",\"0033\"\r\n" + "^SCFG: \"Tcp/MR\",\"00310030\"\r\n" + "^SCFG: \"Tcp/OT\",\"0036003000300030\"\r\n" + "^SCFG: \"Tcp/WithURCs\",\"006F006E\"\r\n" + "^SCFG: \"Trace/Syslog/OTAP\",\"0030\"\r\n" + "^SCFG: \"Urc/Ringline\",\"006C006F00630061006C\"\r\n" + "^SCFG: \"Urc/Ringline/ActiveTime\",\"0032\"\r\n" + "^SCFG: \"Userware/Autostart\",\"0030\"\r\n" + "^SCFG: \"Userware/Autostart/Delay\",\"0030\"\r\n" + "^SCFG: \"Userware/DebugInterface\",\"0030002E0030002E0030002E0030\",\"0030002E0030002E0030002E0030\",\"0030\"\r\n" + "^SCFG: \"Userware/DebugMode\",\"006F00660066\"\r\n" + "^SCFG: \"Userware/Passwd\",\r\n" + "^SCFG: \"Userware/Stdout\",\"006E0075006C006C\",,,,\"006F00660066\"\r\n" + "^SCFG: \"Userware/Watchdog\",\"0030\"\r\n" + "^SCFG: \"MEopMode/ExpectDTR\",\"00630075007200720065006E0074\"\r\n" + "^SCFG: \"MEopMode/ExpectDTR\",\"0070006F00770065007200750070\"\r\n"; + + expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9); + single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single); + + common_test_scfg_response (response, MM_MODEM_CHARSET_UCS2, expected_bands, MM_CINTERION_MODEM_FAMILY_IMT, MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE); + + g_array_unref (expected_bands); +} + +static void +test_scfg_response_alas5 (void) +{ + GArray *expected_bands; + MMModemBand single; + const gchar *response = + "^SCFG: \"Audio/Loop\",\"0\"\r\n" + "^SCFG: \"Audio/SvTone\",\"0\"\r\n" + "^SCFG: \"Call/Ecall/AckTimeout\",\"5000\"\r\n" + "^SCFG: \"Call/Ecall/BlockSMSPP\",\"0\"\r\n" + "^SCFG: \"Call/Ecall/Callback\",\"0\"\r\n" + "^SCFG: \"Call/Ecall/CallbackTimeout\",\"43200000\"\r\n" + "^SCFG: \"Call/Ecall/Force\",\"1\"\r\n" + "^SCFG: \"Call/Ecall/Msd\",\"\"\r\n" + "^SCFG: \"Call/Ecall/Pullmode\",\"0\"\r\n" + "^SCFG: \"Call/Ecall/SessionTimeout\",\"20000\"\r\n" + "^SCFG: \"Call/Ecall/StartTimeout\",\"5000\"\r\n" + "^SCFG: \"Call/ECC\",\"0\"\r\n" + "^SCFG: \"Call/Speech/Codec\",\"0\"\r\n" + "^SCFG: \"GPRS/Auth\",\"2\"\r\n" + "^SCFG: \"GPRS/AutoAttach\",\"enabled\"\r\n" + "^SCFG: \"GPRS/MTU/Mode\",\"0\"\r\n" + "^SCFG: \"GPRS/MTU/Size\",1500\r\n" + "^SCFG: \"MEopMode/CFUN\",\"1\",\"1\"\r\n" + "^SCFG: \"MEopMode/CregRoam\",\"0\"\r\n" + "^SCFG: \"MEopMode/Dormancy\",\"0\",\"0\"\r\n" + "^SCFG: \"MEopMode/DTM/Mode\",\"2\"\r\n" + "^SCFG: \"MEopMode/ExpectDTR\",\"current\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"mbim\",\"asc0\"\r\n" + "^SCFG: \"MEopMode/ExpectDTR\",\"powerup\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"mbim\",\"asc0\"\r\n" + "^SCFG: \"MEopMode/FGI/Split\",\"1\"\r\n" + "^SCFG: \"MEopMode/IMS\",\"1\"\r\n" + "^SCFG: \"MEopMode/NonBlock/Cops\",\"0\"\r\n" + "^SCFG: \"MEopMode/PowerMgmt/LCI\",\"disabled\"\r\n" + "^SCFG: \"MEopMode/Prov/AutoFallback\",\"off\"\r\n" + "^SCFG: \"MEopMode/Prov/AutoSelect\",\"on\"\r\n" + "^SCFG: \"MEopMode/Prov/Cfg\",\"vdfde\"\r\n" + "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"52\",\"50\",\"CPU-A\",\"powerup\"\r\n" + "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"52\",\"50\",\"CPU-A\",\"current\"\r\n" + "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"0\",\"0\",\"CPU-M\",\"powerup\"\r\n" + "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"0\",\"0\",\"CPU-M\",\"current\"\r\n" + "^SCFG: \"MEopMode/SRPOM\",\"0\"\r\n" + "^SCFG: \"MEopMode/USB/KeepData\",\"current\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n" + "^SCFG: \"MEopMode/USB/KeepData\",\"powerup\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n" + "^SCFG: \"MEShutdown/OnIgnition\",\"off\"\r\n" + "^SCFG: \"MEShutdown/Timer\",\"off\"\r\n" + "^SCFG: \"Misc/CId\",\"\"\r\n" + "^SCFG: \"Radio/Band/2G\",\"0000000f\"\r\n" + "^SCFG: \"Radio/Band/3G\",\"000400b5\"\r\n" + "^SCFG: \"Radio/Band/4G\",\"8a0e00d5\",\"000000e2\"\r\n" + "^SCFG: \"Radio/CNS\",\"0\"\r\n" + "^SCFG: \"Radio/Mtpl\",\"0\"\r\n" + "^SCFG: \"Radio/Mtpl/2G\",\"0\"\r\n" + "^SCFG: \"Radio/Mtpl/3G\",\"0\"\r\n" + "^SCFG: \"Radio/Mtpl/4G\",\"0\"\r\n" + "^SCFG: \"Radio/OutputPowerReduction\",\"4\"\r\n" + "^SCFG: \"RemoteWakeUp/Event/ASC\",\"none\"\r\n" + "^SCFG: \"RemoteWakeUp/Event/URC\",\"none\"\r\n" + "^SCFG: \"RemoteWakeUp/Event/USB\",\"GPIO4\"\r\n" + "^SCFG: \"RemoteWakeUp/Ports\",\"current\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n" + "^SCFG: \"RemoteWakeUp/Ports\",\"powerup\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n" + "^SCFG: \"RemoteWakeUp/Pulse\",\"10\"\r\n" + "^SCFG: \"Serial/USB/DDD\",\"0\",\"0\",\"0409\",\"1e2d\",\"0065\",\"Cinterion\",\"LTE Modem\",\"8d8f\"\r\n" + "^SCFG: \"SIM/CS\",\"SIM1\"\r\n" + "^SCFG: \"SMS/4GPREF\",\"IMS\"\r\n" + "^SCFG: \"SMS/AutoAck\",\"0\"\r\n" + "^SCFG: \"SMS/RETRM\",\"30\"\r\n" + "^SCFG: \"URC/Ringline\",\"local\"\r\n" + "^SCFG: \"URC/Ringline/ActiveTime\",\"2\"\r\n"; + + expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 25); + single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_3, g_array_append_val (expected_bands, single); // + single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_6, g_array_append_val (expected_bands, single); // + single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_26, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_38, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_39, g_array_append_val (expected_bands, single); + single = MM_MODEM_BAND_EUTRAN_40, g_array_append_val (expected_bands, single); + + common_test_scfg_response (response, MM_MODEM_CHARSET_GSM, expected_bands, MM_CINTERION_MODEM_FAMILY_DEFAULT, MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE); + + g_array_unref (expected_bands); +} + +/*****************************************************************************/ +/* Test ^SCFG test */ + +static void +compare_arrays (const GArray *supported, + const GArray *expected) +{ + guint i; + + g_assert_cmpuint (supported->len, ==, expected->len); + for (i = 0; i < supported->len; i++) { + gboolean found = FALSE; + guint j; + + for (j = 0; j < expected->len && !found; j++) { + if (g_array_index (supported, guint, i) == g_array_index (expected, guint, j)) + found = TRUE; + } + g_assert (found); + } +} + +static void +common_test_cnmi (const gchar *response, + const GArray *expected_mode, + const GArray *expected_mt, + const GArray *expected_bm, + const GArray *expected_ds, + const GArray *expected_bfr) +{ + GArray *supported_mode = NULL; + GArray *supported_mt = NULL; + GArray *supported_bm = NULL; + GArray *supported_ds = NULL; + GArray *supported_bfr = NULL; + GError *error = NULL; + gboolean res; + + g_assert (expected_mode != NULL); + g_assert (expected_mt != NULL); + g_assert (expected_bm != NULL); + g_assert (expected_ds != NULL); + g_assert (expected_bfr != NULL); + + res = mm_cinterion_parse_cnmi_test (response, + &supported_mode, + &supported_mt, + &supported_bm, + &supported_ds, + &supported_bfr, + &error); + g_assert_no_error (error); + g_assert (res == TRUE); + g_assert (supported_mode != NULL); + g_assert (supported_mt != NULL); + g_assert (supported_bm != NULL); + g_assert (supported_ds != NULL); + g_assert (supported_bfr != NULL); + + compare_arrays (supported_mode, expected_mode); + compare_arrays (supported_mt, expected_mt); + compare_arrays (supported_bm, expected_bm); + compare_arrays (supported_ds, expected_ds); + compare_arrays (supported_bfr, expected_bfr); + + g_array_unref (supported_mode); + g_array_unref (supported_mt); + g_array_unref (supported_bm); + g_array_unref (supported_ds); + g_array_unref (supported_bfr); +} + +static void +test_cnmi_phs8 (void) +{ + GArray *expected_mode; + GArray *expected_mt; + GArray *expected_bm; + GArray *expected_ds; + GArray *expected_bfr; + guint val; + const gchar *response = + "+CNMI: (0,1,2),(0,1),(0,2),(0),(1)\r\n" + "\r\n"; + + expected_mode = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3); + val = 0, g_array_append_val (expected_mode, val); + val = 1, g_array_append_val (expected_mode, val); + val = 2, g_array_append_val (expected_mode, val); + + expected_mt = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); + val = 0, g_array_append_val (expected_mt, val); + val = 1, g_array_append_val (expected_mt, val); + + expected_bm = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); + val = 0, g_array_append_val (expected_bm, val); + val = 2, g_array_append_val (expected_bm, val); + + expected_ds = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); + val = 0, g_array_append_val (expected_ds, val); + + expected_bfr = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); + val = 1, g_array_append_val (expected_bfr, val); + + common_test_cnmi (response, + expected_mode, + expected_mt, + expected_bm, + expected_ds, + expected_bfr); + + g_array_unref (expected_mode); + g_array_unref (expected_mt); + g_array_unref (expected_bm); + g_array_unref (expected_ds); + g_array_unref (expected_bfr); +} + +static void +test_cnmi_other (void) +{ + GArray *expected_mode; + GArray *expected_mt; + GArray *expected_bm; + GArray *expected_ds; + GArray *expected_bfr; + guint val; + const gchar *response = + "+CNMI: (0-3),(0,1),(0,2,3),(0,2),(1)\r\n" + "\r\n"; + + expected_mode = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3); + val = 0, g_array_append_val (expected_mode, val); + val = 1, g_array_append_val (expected_mode, val); + val = 2, g_array_append_val (expected_mode, val); + val = 3, g_array_append_val (expected_mode, val); + + expected_mt = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); + val = 0, g_array_append_val (expected_mt, val); + val = 1, g_array_append_val (expected_mt, val); + + expected_bm = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); + val = 0, g_array_append_val (expected_bm, val); + val = 2, g_array_append_val (expected_bm, val); + val = 3, g_array_append_val (expected_bm, val); + + expected_ds = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); + val = 0, g_array_append_val (expected_ds, val); + val = 2, g_array_append_val (expected_ds, val); + + expected_bfr = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); + val = 1, g_array_append_val (expected_bfr, val); + + common_test_cnmi (response, + expected_mode, + expected_mt, + expected_bm, + expected_ds, + expected_bfr); + + g_array_unref (expected_mode); + g_array_unref (expected_mt); + g_array_unref (expected_bm); + g_array_unref (expected_ds); + g_array_unref (expected_bfr); +} + +/*****************************************************************************/ +/* Test ^SWWAN read */ + +#define SWWAN_TEST_MAX_CIDS 2 + +typedef struct { + guint cid; + MMBearerConnectionStatus state; +} PdpContextState; + +typedef struct { + const gchar *response; + PdpContextState expected_items[SWWAN_TEST_MAX_CIDS]; + gboolean skip_test_other_cids; +} SwwanTest; + +/* Note: all tests are based on checking CIDs 2 and 3 */ +static const SwwanTest swwan_tests[] = { + /* No active PDP context reported (all disconnected) */ + { + .response = "", + .expected_items = { + { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }, + { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED } + }, + /* Don't test other CIDs because for those we would also return + * DISCONNECTED, not UNKNOWN. */ + .skip_test_other_cids = TRUE + }, + /* Single PDP context active (short version without interface index) */ + { + .response = "^SWWAN: 3,1\r\n", + .expected_items = { + { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN }, + { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED } + } + }, + /* Single PDP context active (long version with interface index) */ + { + .response = "^SWWAN: 3,1,1\r\n", + .expected_items = { + { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN }, + { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED } + } + }, + /* Single PDP context inactive (short version without interface index) */ + { + .response = "^SWWAN: 3,0\r\n", + .expected_items = { + { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN }, + { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED } + } + }, + /* Single PDP context inactive (long version with interface index) */ + { + .response = "^SWWAN: 3,0,1\r\n", + .expected_items = { + { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN }, + { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED } + } + }, + /* Multiple PDP contexts active (short version without interface index) */ + { + .response = "^SWWAN: 2,1\r\n^SWWAN: 3,1\r\n", + .expected_items = { + { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }, + { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED } + } + }, + /* Multiple PDP contexts active (long version with interface index) */ + { + .response = "^SWWAN: 2,1,3\r\n^SWWAN: 3,1,1\r\n", + .expected_items = { + { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }, + { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED } + } + }, + /* Multiple PDP contexts inactive (short version without interface index) */ + { + .response = "^SWWAN: 2,0\r\n^SWWAN: 3,0\r\n", + .expected_items = { + { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }, + { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED } + } + }, + /* Multiple PDP contexts inactive (long version with interface index) */ + { + .response = "^SWWAN: 2,0,3\r\n^SWWAN: 3,0,1\r\n", + .expected_items = { + { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }, + { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED } + } + }, + /* Multiple PDP contexts active/inactive (short version without interface index) */ + { + .response = "^SWWAN: 2,0\r\n^SWWAN: 3,1\r\n", + .expected_items = { + { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }, + { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED } + } + }, + /* Multiple PDP contexts active/inactive (long version with interface index) */ + { + .response = "^SWWAN: 2,0,3\r\n^SWWAN: 3,1,1\r\n", + .expected_items = { + { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }, + { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED } + } + } +}; + +static void +test_swwan_pls8 (void) +{ + MMBearerConnectionStatus read_state; + GError *error = NULL; + guint i; + + /* Base tests for successful responses */ + for (i = 0; i < G_N_ELEMENTS (swwan_tests); i++) { + guint j; + + /* Query for the expected items (CIDs 2 and 3) */ + for (j = 0; j < SWWAN_TEST_MAX_CIDS; j++) { + read_state = mm_cinterion_parse_swwan_response (swwan_tests[i].response, swwan_tests[i].expected_items[j].cid, NULL, &error); + if (swwan_tests[i].expected_items[j].state == MM_BEARER_CONNECTION_STATUS_UNKNOWN) { + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED); + g_clear_error (&error); + } else + g_assert_no_error (error); + g_assert_cmpint (read_state, ==, swwan_tests[i].expected_items[j].state); + } + + /* Query for a CID which isn't replied (e.g. 12) */ + if (!swwan_tests[i].skip_test_other_cids) { + read_state = mm_cinterion_parse_swwan_response (swwan_tests[i].response, 12, NULL, &error); + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED); + g_assert_cmpint (read_state, ==, MM_BEARER_CONNECTION_STATUS_UNKNOWN); + g_clear_error (&error); + } + } + + /* Additional tests for errors */ + read_state = mm_cinterion_parse_swwan_response ("^GARBAGE", 2, NULL, &error); + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED); + g_assert_cmpint (read_state, ==, MM_BEARER_CONNECTION_STATUS_UNKNOWN); + g_clear_error (&error); +} + +/*****************************************************************************/ +/* Test ^SIND responses */ + +static void +common_test_sind_response (const gchar *response, + const gchar *expected_description, + guint expected_mode, + guint expected_value) +{ + GError *error = NULL; + gboolean res; + gchar *description; + guint mode; + guint value; + + res = mm_cinterion_parse_sind_response (response, + &description, + &mode, + &value, + &error); + g_assert_no_error (error); + g_assert (res == TRUE); + + g_assert_cmpstr (description, ==, expected_description); + g_assert_cmpuint (mode, ==, expected_mode); + g_assert_cmpuint (value, ==, expected_value); + + g_free (description); +} + +static void +test_sind_response_simstatus (void) +{ + common_test_sind_response ("^SIND: simstatus,1,5", "simstatus", 1, 5); +} + +/*****************************************************************************/ +/* Test ^SMONG responses */ + +static void +common_test_smong_response (const gchar *response, + gboolean success, + MMModemAccessTechnology expected_access_tech) +{ + GError *error = NULL; + gboolean res; + MMModemAccessTechnology access_tech; + + res = mm_cinterion_parse_smong_response (response, &access_tech, &error); + + if (success) { + g_assert_no_error (error); + g_assert (res); + g_assert_cmpuint (access_tech, ==, expected_access_tech); + } else { + g_assert (error); + g_assert (!res); + } +} + +static void +test_smong_response_tc63i (void) +{ + const gchar *response = + "\r\n" + "GPRS Monitor\r\n" + "BCCH G PBCCH PAT MCC MNC NOM TA RAC # Cell #\r\n" + "0073 1 - - 262 02 2 00 01\r\n"; + common_test_smong_response (response, TRUE, MM_MODEM_ACCESS_TECHNOLOGY_GPRS); +} + +static void +test_smong_response_other (void) +{ + const gchar *response = + "\r\n" + "GPRS Monitor\r\n" + "\r\n" + "BCCH G PBCCH PAT MCC MNC NOM TA RAC # Cell #\r\n" + " 44 1 - - 234 10 - - - \r\n"; + common_test_smong_response (response, TRUE, MM_MODEM_ACCESS_TECHNOLOGY_GPRS); +} + +static void +test_smong_response_no_match (void) +{ + const gchar *response = + "\r\n" + "GPRS Monitor\r\n" + "\r\n" + "BCCH K PBCCH PAT MCC MNC NOM TA RAC # Cell #\r\n" + " 44 1 - - 234 10 - - - \r\n"; + common_test_smong_response (response, FALSE, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); +} + +/*****************************************************************************/ +/* Test ^SLCC URCs */ + +static void +common_test_slcc_urc (const gchar *urc, + const MMCallInfo *expected_call_info_list, + guint expected_call_info_list_size) +{ + g_autoptr(GRegex) slcc_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *str = NULL; + GError *error = NULL; + GList *call_info_list = NULL; + GList *l; + gboolean result; + + slcc_regex = mm_cinterion_get_slcc_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (slcc_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + /* read full matched content */ + str = g_match_info_fetch (match_info, 0); + g_assert (str); + + result = mm_cinterion_parse_slcc_list (str, NULL, &call_info_list, &error); + g_assert_no_error (error); + g_assert (result); + + g_debug ("found %u calls", g_list_length (call_info_list)); + + if (expected_call_info_list) { + g_assert (call_info_list); + g_assert_cmpuint (g_list_length (call_info_list), ==, expected_call_info_list_size); + } else + g_assert (!call_info_list); + + for (l = call_info_list; l; l = g_list_next (l)) { + const MMCallInfo *call_info = (const MMCallInfo *)(l->data); + gboolean found = FALSE; + guint i; + + g_debug ("call at index %u: direction %s, state %s, number %s", + call_info->index, + mm_call_direction_get_string (call_info->direction), + mm_call_state_get_string (call_info->state), + call_info->number ? call_info->number : "n/a"); + + for (i = 0; !found && i < expected_call_info_list_size; i++) + found = ((call_info->index == expected_call_info_list[i].index) && + (call_info->direction == expected_call_info_list[i].direction) && + (call_info->state == expected_call_info_list[i].state) && + (g_strcmp0 (call_info->number, expected_call_info_list[i].number) == 0)); + + g_assert (found); + } + + mm_cinterion_call_info_list_free (call_info_list); +} + +static void +test_slcc_urc_empty (void) +{ + const gchar *urc = "\r\n^SLCC: \r\n"; + + common_test_slcc_urc (urc, NULL, 0); +} + +static void +test_slcc_urc_single (void) +{ + static const MMCallInfo expected_call_info_list[] = { + { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" } + }; + + const gchar *urc = + "\r\n^SLCC: 1,1,0,0,0,0,\"123456789\",161" + "\r\n^SLCC: \r\n"; + + common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list)); +} + +static void +test_slcc_urc_multiple (void) +{ + static const MMCallInfo expected_call_info_list[] = { + { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, NULL }, + { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" }, + { 3, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "987654321" }, + }; + + const gchar *urc = + "\r\n^SLCC: 1,1,0,0,1,0" /* number unknown */ + "\r\n^SLCC: 2,1,0,0,1,0,\"123456789\",161" + "\r\n^SLCC: 3,1,0,0,1,0,\"987654321\",161,\"Alice\"" + "\r\n^SLCC: \r\n"; + + common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list)); +} + +static void +test_slcc_urc_complex (void) +{ + static const MMCallInfo expected_call_info_list[] = { + { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" }, + { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_WAITING, (gchar *) "987654321" }, + }; + + const gchar *urc = + "\r\n^CIEV: 1,0" /* some different URC before our match */ + "\r\n^SLCC: 1,1,0,0,0,0,\"123456789\",161" + "\r\n^SLCC: 2,1,5,0,0,0,\"987654321\",161" + "\r\n^SLCC: \r\n" + "\r\n^CIEV: 1,0" /* some different URC after our match */; + + common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list)); +} + +/*****************************************************************************/ +/* Test +CTZU URCs */ + +static void +common_test_ctzu_urc (const gchar *urc, + const gchar *expected_iso8601, + gint expected_offset, + gint expected_dst_offset) +{ + g_autoptr(GRegex) ctzu_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *iso8601 = NULL; + GError *error = NULL; + gboolean result; + MMNetworkTimezone *tz = NULL; + + ctzu_regex = mm_cinterion_get_ctzu_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (ctzu_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + result = mm_cinterion_parse_ctzu_urc (match_info, &iso8601, &tz, &error); + g_assert_no_error (error); + g_assert (result); + + g_assert (iso8601); + g_assert_cmpstr (expected_iso8601, ==, iso8601); + + g_assert (tz); + g_assert_cmpint (expected_offset, ==, mm_network_timezone_get_offset (tz)); + + if (expected_dst_offset >= 0) + g_assert_cmpuint ((guint)expected_dst_offset, ==, mm_network_timezone_get_dst_offset (tz)); + + g_object_unref (tz); +} + +static void +test_ctzu_urc_simple (void) +{ + const gchar *urc = "\r\n+CTZU: \"19/07/09,11:15:40\",+08\r\n"; + const gchar *expected_iso8601 = "2019-07-09T11:15:40+02"; + gint expected_offset = 120; + gint expected_dst_offset = -1; /* not given */ + + common_test_ctzu_urc (urc, expected_iso8601, expected_offset, expected_dst_offset); +} + +static void +test_ctzu_urc_full (void) +{ + const gchar *urc = "\r\n+CTZU: \"19/07/09,11:15:40\",+08,1\r\n"; + const gchar *expected_iso8601 = "2019-07-09T11:15:40+02"; + gint expected_offset = 120; + gint expected_dst_offset = 60; + + common_test_ctzu_urc (urc, expected_iso8601, expected_offset, expected_dst_offset); +} + +/*****************************************************************************/ +/* Test ^SMONI responses */ + +typedef struct { + const gchar *str; + MMCinterionRadioGen tech; + gdouble rssi; + gdouble ecn0; + gdouble rscp; + gdouble rsrp; + gdouble rsrq; +} SMoniResponseTest; + +static const SMoniResponseTest smoni_response_tests[] = { + { + .str = "^SMONI: 2G,71,-61,262,02,0143,83BA,33,33,3,6,G,NOCONN", + .tech = MM_CINTERION_RADIO_GEN_2G, + .rssi = -61.0, + .ecn0 = 0.0, + .rscp = 0.0, + .rsrp = 0.0, + .rsrq = 0.0 + }, + { + .str = "^SMONI: 2G,SEARCH,SEARCH", + .tech = MM_CINTERION_RADIO_GEN_NONE, + .rssi = 0.0, + .ecn0 = 0.0, + .rscp = 0.0, + .rsrp = 0.0, + .rsrq = 0.0 + }, + { + .str = "^SMONI: 2G,673,-89,262,07,4EED,A500,16,16,7,4,G,5,-107,LIMSRV", + .tech = MM_CINTERION_RADIO_GEN_2G, + .rssi = -89.0, + .ecn0 = 0.0, + .rscp = 0.0, + .rsrp = 0.0, + .rsrq = 0.0 + }, + { + .str = "^SMONI: 2G,673,-80,262,07,4EED,A500,35,35,7,4,G,643,4,0,-80,0,S_FR", + .tech = MM_CINTERION_RADIO_GEN_2G, + .rssi = -80.0, + .ecn0 = 0.0, + .rscp = 0.0, + .rsrp = 0.0, + .rsrq = 0.0 + }, + { + .str = "^SMONI: 3G,10564,296,-7.5,-79,262,02,0143,00228FF,-92,-78,NOCONN", + .tech = MM_CINTERION_RADIO_GEN_3G, + .rssi = 0.0, + .ecn0 = -7.5, + .rscp = -79.0, + .rsrp = 0.0, + .rsrq = 0.0 + }, + { + .str = "^SMONI: 3G,SEARCH,SEARCH", + .tech = MM_CINTERION_RADIO_GEN_NONE, + .rssi = 0.0, + .ecn0 = 0, + .rscp = 0, + .rsrp = 0.0, + .rsrq = 0.0 + }, + { + .str = "^SMONI: 3G,10564,96,-6.5,-77,262,02,0143,00228FF,-92,-78,LIMSRV", + .tech = MM_CINTERION_RADIO_GEN_3G, + .rssi = 0.0, + .ecn0 = -6.5, + .rscp = -77.0, + .rsrp = 0.0, + .rsrq = 0.0 + }, + { + .str = "^SMONI: 3G,10737,131,-5,-93,260,01,7D3D,C80BC9A,--,--,----,---,-,-5,-93,0,01,06", + .tech = MM_CINTERION_RADIO_GEN_3G, + .rssi = 0.0, + .ecn0 = -5.0, + .rscp = -93.0, + .rsrp = 0.0, + .rsrq = 0.0 + }, + { + .str = "^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-94,-7,NOCONN", + .tech = MM_CINTERION_RADIO_GEN_4G, + .rssi = 0.0, + .ecn0 = 0.0, + .rscp = 0.0, + .rsrp = -94.0, + .rsrq = -7.0 + }, + { + .str = "^SMONI: 4G,SEARCH", + .tech = MM_CINTERION_RADIO_GEN_NONE, + .rssi = 0.0, + .ecn0 = 0.0, + .rscp = 0.0, + .rsrp = 0.0, + .rsrq = 0.0 + }, + { + .str = "^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-90,-6,LIMSRV", + .tech = MM_CINTERION_RADIO_GEN_4G, + .rssi = 0.0, + .ecn0 = 0.0, + .rscp = 0.0, + .rsrp = -90.0, + .rsrq = -6.0 + }, + { + .str = "^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,90,-101,-7,CONN", + .tech = MM_CINTERION_RADIO_GEN_4G, + .rssi = 0.0, + .ecn0 = 0.0, + .rscp = 0.0, + .rsrp = -101.0, + .rsrq = -7.0 + }, + { + .str = "^SMONI: 4G,2850,7,20,20,FDD,262,02,C096,027430F,275,11,-114,-9,NOCONN", + .tech = MM_CINTERION_RADIO_GEN_4G, + .rssi = 0.0, + .ecn0 = 0.0, + .rscp = 0.0, + .rsrp = -114.0, + .rsrq = -9.0 + }, + { + .str = "^SMONI: 4G,2850,7,20,20,FDD,262,02,C096,027430F,275,-,-113,-8,CONN", + .tech = MM_CINTERION_RADIO_GEN_4G, + .rssi = 0.0, + .ecn0 = 0.0, + .rscp = 0.0, + .rsrp = -113.0, + .rsrq = -8.0 + } +}; + +static void +test_smoni_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (smoni_response_tests); i++) { + GError *error = NULL; + gboolean success; + MMCinterionRadioGen tech = MM_CINTERION_RADIO_GEN_NONE; + gdouble rssi = MM_SIGNAL_UNKNOWN; + gdouble ecn0 = MM_SIGNAL_UNKNOWN; + gdouble rscp = MM_SIGNAL_UNKNOWN; + gdouble rsrp = MM_SIGNAL_UNKNOWN; + gdouble rsrq = MM_SIGNAL_UNKNOWN; + + success = mm_cinterion_parse_smoni_query_response (smoni_response_tests[i].str, + &tech, &rssi, + &ecn0, &rscp, + &rsrp, &rsrq, + &error); + g_assert_no_error (error); + g_assert (success); + + g_assert_cmpuint (smoni_response_tests[i].tech, ==, tech); + switch (smoni_response_tests[i].tech) { + case MM_CINTERION_RADIO_GEN_2G: + g_assert_cmpfloat_tolerance (rssi, smoni_response_tests[i].rssi, 0.1); + break; + case MM_CINTERION_RADIO_GEN_3G: + g_assert_cmpfloat_tolerance (ecn0, smoni_response_tests[i].ecn0, 0.1); + g_assert_cmpfloat_tolerance (rscp, smoni_response_tests[i].rscp, 0.1); + break; + case MM_CINTERION_RADIO_GEN_4G: + g_assert_cmpfloat_tolerance (rsrp, smoni_response_tests[i].rsrp, 0.1); + g_assert_cmpfloat_tolerance (rsrq, smoni_response_tests[i].rsrq, 0.1); + break; + case MM_CINTERION_RADIO_GEN_NONE: + default: + break; + } + } +} + +static void +test_smoni_response_to_signal (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (smoni_response_tests); i++) { + GError *error = NULL; + gboolean success; + MMSignal *gsm = NULL; + MMSignal *umts = NULL; + MMSignal *lte = NULL; + + success = mm_cinterion_smoni_response_to_signal_info (smoni_response_tests[i].str, + &gsm, &umts, <e, + &error); + g_assert_no_error (error); + g_assert (success); + + switch (smoni_response_tests[i].tech) { + case MM_CINTERION_RADIO_GEN_2G: + g_assert (gsm); + g_assert_cmpfloat_tolerance (mm_signal_get_rssi (gsm), smoni_response_tests[i].rssi, 0.1); + g_object_unref (gsm); + g_assert (!umts); + g_assert (!lte); + break; + case MM_CINTERION_RADIO_GEN_3G: + g_assert (umts); + g_assert_cmpfloat_tolerance (mm_signal_get_rscp (umts), smoni_response_tests[i].rscp, 0.1); + g_assert_cmpfloat_tolerance (mm_signal_get_ecio (umts), smoni_response_tests[i].ecn0, 0.1); + g_object_unref (umts); + g_assert (!gsm); + g_assert (!lte); + break; + case MM_CINTERION_RADIO_GEN_4G: + g_assert (lte); + g_assert_cmpfloat_tolerance (mm_signal_get_rsrp (lte), smoni_response_tests[i].rsrp, 0.1); + g_assert_cmpfloat_tolerance (mm_signal_get_rsrq (lte), smoni_response_tests[i].rsrq, 0.1); + g_object_unref (lte); + g_assert (!gsm); + g_assert (!umts); + break; + case MM_CINTERION_RADIO_GEN_NONE: + default: + g_assert (!gsm); + g_assert (!umts); + g_assert (!lte); + break; + } + } +} + +/*****************************************************************************/ +/* Test ^SCFG="MEopMode/Prov/Cfg" responses */ + +typedef struct { + const gchar *str; + MMCinterionModemFamily modem_family; + gboolean success; + guint expected_cid; +} ProvcfgResponseTest; + +static const ProvcfgResponseTest provcfg_response_tests[] = { + { + + .str = "^SCFG: \"MEopMode/Prov/Cfg\",\"vdfde\"", + .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT, + .success = TRUE, + .expected_cid = 1, + }, + { + + .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"attus\"", + .modem_family = MM_CINTERION_MODEM_FAMILY_IMT, + .success = TRUE, + .expected_cid = 1, + }, + { + + .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"2\"", + .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT, + .success = TRUE, + .expected_cid = 3, + }, + { + + .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"vzwdcus\"", + .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT, + .success = TRUE, + .expected_cid = 3, + }, + { + + .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"tmode\"", + .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT, + .success = TRUE, + .expected_cid = 2, + }, + { + .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"fallback*\"", + .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT, + .success = TRUE, + .expected_cid = 1, + }, + { + /* commas not allowed by the regex */ + .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"something,with,commas\"", + .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT, + .success = FALSE, + } +}; + +static void +test_provcfg_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (provcfg_response_tests); i++) { + gint cid = -1; + gboolean result; + GError *error = NULL; + + result = mm_cinterion_provcfg_response_to_cid (provcfg_response_tests[i].str, + provcfg_response_tests[i].modem_family, + MM_MODEM_CHARSET_GSM, + NULL, + &cid, + &error); + if (provcfg_response_tests[i].success) { + g_assert_no_error (error); + g_assert (result); + g_assert_cmpuint (cid, ==, provcfg_response_tests[i].expected_cid); + } else { + g_assert (error); + g_assert (!result); + } + } +} + +/*****************************************************************************/ +/* Test ^SGAUTH responses */ + +static void +test_sgauth_response (void) +{ + gboolean result; + MMBearerAllowedAuth auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN; + gchar *username = NULL; + GError *error = NULL; + + const gchar *response = + "^SGAUTH: 1,2,\"vf\"\r\n" + "^SGAUTH: 2,1,\"\"\r\n" + "^SGAUTH: 3,0\r\n"; + + /* CID 1 */ + result = mm_cinterion_parse_sgauth_response (response, 1, &auth, &username, &error); + g_assert_no_error (error); + g_assert (result); + g_assert_cmpuint (auth, ==, MM_BEARER_ALLOWED_AUTH_CHAP); + g_assert_cmpstr (username, ==, "vf"); + + auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN; + g_clear_pointer (&username, g_free); + + /* CID 2 */ + result = mm_cinterion_parse_sgauth_response (response, 2, &auth, &username, &error); + g_assert_no_error (error); + g_assert (result); + g_assert_cmpuint (auth, ==, MM_BEARER_ALLOWED_AUTH_PAP); + g_assert_null (username); + + auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN; + + /* CID 3 */ + result = mm_cinterion_parse_sgauth_response (response, 3, &auth, &username, &error); + g_assert_no_error (error); + g_assert (result); + g_assert_cmpuint (auth, ==, MM_BEARER_ALLOWED_AUTH_NONE); + g_assert_null (username); + + auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN; + + /* CID 4 */ + result = mm_cinterion_parse_sgauth_response (response, 4, &auth, &username, &error); + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND); + g_assert (!result); +} + +/*****************************************************************************/ +/* Test ^SXRAT responses */ + +static void +common_test_sxrat (const gchar *response, + const GArray *expected_rat, + const GArray *expected_pref1, + const GArray *expected_pref2) +{ + GArray *supported_rat = NULL; + GArray *supported_pref1 = NULL; + GArray *supported_pref2 = NULL; + GError *error = NULL; + gboolean res; + + g_assert (expected_rat != NULL); + g_assert (expected_pref1 != NULL); + + res = mm_cinterion_parse_sxrat_test (response, + &supported_rat, + &supported_pref1, + &supported_pref2, + &error); + g_assert_no_error (error); + g_assert (res == TRUE); + g_assert (supported_rat != NULL); + g_assert (supported_pref1 != NULL); + if (expected_pref2) + g_assert (supported_pref2 != NULL); + else + g_assert (supported_pref2 == NULL); + + compare_arrays (supported_rat, expected_rat); + compare_arrays (supported_pref1, expected_pref1); + if (expected_pref2) + compare_arrays (supported_pref2, expected_pref2); + + g_array_unref (supported_rat); + g_array_unref (supported_pref1); + if (supported_pref2) + g_array_unref (supported_pref2); +} + +static void +test_sxrat_response_els61 (void) +{ + GArray *expected_rat; + GArray *expected_pref1; + GArray *expected_pref2; + guint val; + const gchar *response = + "^SXRAT: (0-6),(0,2,3),(0,2,3)\r\n" + "\r\n"; + + expected_rat = g_array_sized_new (FALSE, FALSE, sizeof (guint), 7); + val = 0, g_array_append_val (expected_rat, val); + val = 1, g_array_append_val (expected_rat, val); + val = 2, g_array_append_val (expected_rat, val); + val = 3, g_array_append_val (expected_rat, val); + val = 4, g_array_append_val (expected_rat, val); + val = 5, g_array_append_val (expected_rat, val); + val = 6, g_array_append_val (expected_rat, val); + + expected_pref1 = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3); + val = 0, g_array_append_val (expected_pref1, val); + val = 2, g_array_append_val (expected_pref1, val); + val = 3, g_array_append_val (expected_pref1, val); + + expected_pref2 = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3); + val = 0, g_array_append_val (expected_pref2, val); + val = 2, g_array_append_val (expected_pref2, val); + val = 3, g_array_append_val (expected_pref2, val); + + common_test_sxrat (response, + expected_rat, + expected_pref1, + expected_pref2); + + g_array_unref (expected_rat); + g_array_unref (expected_pref1); + g_array_unref (expected_pref2); +} + +static void +test_sxrat_response_other (void) +{ + GArray *expected_rat; + GArray *expected_pref1; + GArray *expected_pref2 = NULL; + guint val; + const gchar *response = + "^SXRAT: (0-2),(0,2)\r\n" + "\r\n"; + + expected_rat = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3); + val = 0, g_array_append_val (expected_rat, val); + val = 1, g_array_append_val (expected_rat, val); + val = 2, g_array_append_val (expected_rat, val); + + expected_pref1 = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3); + val = 0, g_array_append_val (expected_pref1, val); + val = 2, g_array_append_val (expected_pref1, val); + + common_test_sxrat (response, + expected_rat, + expected_pref1, + expected_pref2); + + g_array_unref (expected_rat); + g_array_unref (expected_pref1); +} + +typedef struct { + const gchar *str; + MMModemMode allowed; + MMModemMode preferred; + gboolean success; +} SxratBuildTest; + +static const SxratBuildTest sxrat_build_tests[] = { + { + .str = "^SXRAT=0", + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE, + .success = TRUE, + }, + { + .str = "^SXRAT=3", + .allowed = MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_NONE, + .success = TRUE, + }, + { + .str = "^SXRAT=1,2", + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_3G, + .success = TRUE, + }, + { + .str = "^SXRAT=6,3", + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_4G, + .success = TRUE, + }, + { + .str = NULL, + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .success = FALSE, + }, + { + .str = NULL, + .allowed = MM_MODEM_MODE_5G, + .preferred = MM_MODEM_MODE_NONE, + .success = FALSE, + }, + +}; + +static void +test_sxrat (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (sxrat_build_tests); i++) { + GError *error = NULL; + gchar* result; + + result = mm_cinterion_build_sxrat_set_command (sxrat_build_tests[i].allowed, + sxrat_build_tests[i].preferred, + &error); + if (sxrat_build_tests[i].success) { + g_assert_no_error (error); + g_assert (result); + g_assert_cmpstr (result, ==, sxrat_build_tests[i].str); + } else { + g_assert (error); + g_assert (!result); + } + } +} +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/cinterion/scfg", test_scfg); + g_test_add_func ("/MM/cinterion/scfg/ehs5", test_scfg_ehs5); + g_test_add_func ("/MM/cinterion/scfg/pls62/gsm", test_scfg_pls62_gsm); + g_test_add_func ("/MM/cinterion/scfg/pls62/ucs2", test_scfg_pls62_ucs2); + g_test_add_func ("/MM/cinterion/scfg/alas5", test_scfg_alas5); + g_test_add_func ("/MM/cinterion/scfg/response/3g", test_scfg_response_3g); + g_test_add_func ("/MM/cinterion/scfg/response/2g", test_scfg_response_2g); + g_test_add_func ("/MM/cinterion/scfg/response/pls62/gsm", test_scfg_response_pls62_gsm); + g_test_add_func ("/MM/cinterion/scfg/response/pls62/ucs2",test_scfg_response_pls62_ucs2); + g_test_add_func ("/MM/cinterion/scfg/response/alas5", test_scfg_response_alas5); + g_test_add_func ("/MM/cinterion/cnmi/phs8", test_cnmi_phs8); + g_test_add_func ("/MM/cinterion/cnmi/other", test_cnmi_other); + g_test_add_func ("/MM/cinterion/swwan/pls8", test_swwan_pls8); + g_test_add_func ("/MM/cinterion/sind/response/simstatus", test_sind_response_simstatus); + g_test_add_func ("/MM/cinterion/smong/response/tc63i", test_smong_response_tc63i); + g_test_add_func ("/MM/cinterion/smong/response/other", test_smong_response_other); + g_test_add_func ("/MM/cinterion/smong/response/no-match", test_smong_response_no_match); + g_test_add_func ("/MM/cinterion/slcc/urc/empty", test_slcc_urc_empty); + g_test_add_func ("/MM/cinterion/slcc/urc/single", test_slcc_urc_single); + g_test_add_func ("/MM/cinterion/slcc/urc/multiple", test_slcc_urc_multiple); + g_test_add_func ("/MM/cinterion/slcc/urc/complex", test_slcc_urc_complex); + g_test_add_func ("/MM/cinterion/ctzu/urc/simple", test_ctzu_urc_simple); + g_test_add_func ("/MM/cinterion/ctzu/urc/full", test_ctzu_urc_full); + g_test_add_func ("/MM/cinterion/smoni/query_response", test_smoni_response); + g_test_add_func ("/MM/cinterion/smoni/query_response_to_signal", test_smoni_response_to_signal); + g_test_add_func ("/MM/cinterion/scfg/provcfg", test_provcfg_response); + g_test_add_func ("/MM/cinterion/sgauth", test_sgauth_response); + g_test_add_func ("/MM/cinterion/sxrat", test_sxrat); + g_test_add_func ("/MM/cinterion/sxrat/response/els61", test_sxrat_response_els61); + g_test_add_func ("/MM/cinterion/sxrat/response/other", test_sxrat_response_other); + + return g_test_run (); +} diff --git a/src/plugins/dell/77-mm-dell-port-types.rules b/src/plugins/dell/77-mm-dell-port-types.rules new file mode 100644 index 00000000..fa01c116 --- /dev/null +++ b/src/plugins/dell/77-mm-dell-port-types.rules @@ -0,0 +1,32 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_dell_port_types_end" + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="413c", GOTO="mm_dell_vendorcheck" +GOTO="mm_dell_port_types_end" + +LABEL="mm_dell_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Dell DW5821e (default 0x81d7, with esim support 0x81e0) +# if 02: primary port +# if 03: secondary port +# if 04: raw NMEA port +# if 05: diag/qcdm port +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" + +# Dell DW5820e +# if 02: AT port +# if 04: debug port (ignore) +# if 06: AT port +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d9", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" + +GOTO="mm_dell_port_types_end" +LABEL="mm_dell_port_types_end" diff --git a/src/plugins/dell/mm-plugin-dell.c b/src/plugins/dell/mm-plugin-dell.c new file mode 100644 index 00000000..63d8b4da --- /dev/null +++ b/src/plugins/dell/mm-plugin-dell.c @@ -0,0 +1,528 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2015-2019 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-dell.h" +#include "mm-common-novatel.h" +#include "mm-private-boxed-types.h" +#include "mm-broadband-modem.h" +#include "mm-broadband-modem-novatel.h" +#include "mm-common-novatel.h" +#include "mm-broadband-modem-sierra.h" +#include "mm-common-sierra.h" +#include "mm-broadband-modem-telit.h" +#include "mm-broadband-modem-xmm.h" +#include "mm-common-telit.h" +#include "mm-log-object.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi.h" +#endif + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#include "mm-broadband-modem-mbim-xmm.h" +#include "mm-broadband-modem-mbim-foxconn.h" +#endif + +#define MAX_PORT_PROBE_TIMEOUTS 3 + +G_DEFINE_TYPE (MMPluginDell, mm_plugin_dell, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +#define TAG_DELL_MANUFACTURER "dell-manufacturer" +typedef enum { + DELL_MANUFACTURER_UNKNOWN = 0, + DELL_MANUFACTURER_NOVATEL = 1, + DELL_MANUFACTURER_SIERRA = 2, + DELL_MANUFACTURER_ERICSSON = 3, + DELL_MANUFACTURER_TELIT = 4 +} DellManufacturer; + +/*****************************************************************************/ +/* Custom init */ + +typedef struct { + MMPortSerialAt *port; + guint gmi_retries; + guint cgmi_retries; + guint ati_retries; + guint timeouts; +} CustomInitContext; + +static void +custom_init_context_free (CustomInitContext *ctx) +{ + g_object_unref (ctx->port); + g_slice_free (CustomInitContext, ctx); +} + +static gboolean +dell_custom_init_finish (MMPortProbe *probe, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +novatel_custom_init_ready (MMPortProbe *probe, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_common_novatel_custom_init_finish (probe, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +sierra_custom_init_ready (MMPortProbe *probe, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_common_sierra_custom_init_finish (probe, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +telit_custom_init_ready (MMPortProbe *probe, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!telit_custom_init_finish (probe, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void custom_init_step (GTask *task); + +static void +custom_init_step_next_command (GTask *task) +{ + CustomInitContext *ctx; + + ctx = g_task_get_task_data (task); + + ctx->timeouts = 0; + if (ctx->gmi_retries > 0) + ctx->gmi_retries = 0; + else if (ctx->cgmi_retries > 0) + ctx->cgmi_retries = 0; + else if (ctx->ati_retries > 0) + ctx->ati_retries = 0; + custom_init_step (task); +} + +static void +response_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + CustomInitContext *ctx; + MMPortProbe *probe; + const gchar *response; + GError *error = NULL; + gchar *lower; + DellManufacturer manufacturer; + + probe = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + response = mm_port_serial_at_command_finish (port, res, &error); + if (error) { + /* Non-timeout error, jump to next command */ + if (!g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { + mm_obj_dbg (probe, "error probing AT port: %s", error->message); + g_error_free (error); + custom_init_step_next_command (task); + return; + } + /* Directly retry same command on timeout */ + ctx->timeouts++; + custom_init_step (task); + g_error_free (error); + return; + } + + /* Guess manufacturer from response */ + lower = g_ascii_strdown (response, -1); + if (strstr (lower, "novatel")) + manufacturer = DELL_MANUFACTURER_NOVATEL; + else if (strstr (lower, "sierra")) + manufacturer = DELL_MANUFACTURER_SIERRA; + else if (strstr (lower, "ericsson")) + manufacturer = DELL_MANUFACTURER_ERICSSON; + else if (strstr (lower, "telit")) + manufacturer = DELL_MANUFACTURER_TELIT; + else + manufacturer = DELL_MANUFACTURER_UNKNOWN; + g_free (lower); + + /* Tag based on manufacturer */ + if (manufacturer != DELL_MANUFACTURER_UNKNOWN) { + g_object_set_data (G_OBJECT (probe), TAG_DELL_MANUFACTURER, GUINT_TO_POINTER (manufacturer)); + + /* Run additional custom init, if needed */ + + if (manufacturer == DELL_MANUFACTURER_NOVATEL) { + mm_common_novatel_custom_init (probe, + ctx->port, + g_task_get_cancellable (task), + (GAsyncReadyCallback) novatel_custom_init_ready, + task); + return; + } + + if (manufacturer == DELL_MANUFACTURER_SIERRA) { + mm_common_sierra_custom_init (probe, + ctx->port, + g_task_get_cancellable (task), + (GAsyncReadyCallback) sierra_custom_init_ready, + task); + return; + } + + if (manufacturer == DELL_MANUFACTURER_TELIT) { + telit_custom_init (probe, + ctx->port, + g_task_get_cancellable (task), + (GAsyncReadyCallback) telit_custom_init_ready, + task); + return; + } + + /* Finish custom_init */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* If we got a response, but we didn't get an expected string, try with next command */ + custom_init_step_next_command (task); +} + +static void +custom_init_step (GTask *task) +{ + CustomInitContext *ctx; + MMPortProbe *probe; + + probe = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* If cancelled, end without error right away */ + if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) { + mm_obj_dbg (probe, "no need to keep on running custom init: cancelled"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + +#if defined WITH_QMI + /* If device has a QMI port, don't run anything else, as we don't care */ + if (mm_port_probe_list_has_qmi_port (mm_device_peek_port_probe_list (mm_port_probe_peek_device (probe)))) { + mm_obj_dbg (probe, "no need to run custom init: device has QMI port"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } +#endif + +#if defined WITH_MBIM + /* If device has a MBIM port, don't run anything else, as we don't care */ + if (mm_port_probe_list_has_mbim_port (mm_device_peek_port_probe_list (mm_port_probe_peek_device (probe)))) { + mm_obj_dbg (probe, "no need to run custom init: device has MBIM port"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } +#endif + + if (ctx->timeouts >= MAX_PORT_PROBE_TIMEOUTS) { + mm_obj_dbg (probe, "couldn't detect real manufacturer: too many timeouts"); + mm_port_probe_set_result_at (probe, FALSE); + goto out; + } + + if (ctx->gmi_retries > 0) { + ctx->gmi_retries--; + mm_port_serial_at_command (ctx->port, + "AT+GMI", + 3, + FALSE, /* raw */ + FALSE, /* allow_cached */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)response_ready, + task); + return; + } + + if (ctx->cgmi_retries > 0) { + ctx->cgmi_retries--; + mm_port_serial_at_command (ctx->port, + "AT+CGMI", + 3, + FALSE, /* raw */ + FALSE, /* allow_cached */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)response_ready, + task); + return; + } + + if (ctx->ati_retries > 0) { + ctx->ati_retries--; + /* Note: in Ericsson devices, ATI3 seems to reply the vendor string */ + mm_port_serial_at_command (ctx->port, + "ATI1I2I3", + 3, + FALSE, /* raw */ + FALSE, /* allow_cached */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)response_ready, + task); + return; + } + + mm_obj_dbg (probe, "couldn't detect real manufacturer: all retries consumed"); +out: + /* Finish custom_init */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +dell_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + CustomInitContext *ctx; + + ctx = g_slice_new0 (CustomInitContext); + ctx->port = g_object_ref (port); + ctx->gmi_retries = 3; + ctx->cgmi_retries = 1; + ctx->ati_retries = 1; + + task = g_task_new (probe, cancellable, callback, user_data); + g_task_set_check_cancellable (task, FALSE); + g_task_set_task_data (task, ctx, (GDestroyNotify) custom_init_context_free); + + custom_init_step (task); +} + +/*****************************************************************************/ + +static gboolean +port_probe_list_has_manufacturer_port (GList *probes, + DellManufacturer manufacturer) +{ + GList *l; + + for (l = probes; l; l = g_list_next (l)) { + if (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (l->data), TAG_DELL_MANUFACTURER)) == manufacturer) + return TRUE; + } + return FALSE; +} + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + /* Note: at this point we don't make any difference between different + * Dell-branded QMI or MBIM modems; they may come from Novatel, Ericsson or + * Sierra. */ + +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Dell-branded modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + /* Specific implementation for the DW5821e and DW5829e */ + if (vendor == 0x413c && (product == 0x81d7 || product == 0x81e0 || product == 0x81e4 || product == 0x81e6)) { + mm_obj_dbg (self, "MBIM-powered DW5821e/DW5829e (T77W968) modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_foxconn_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } + + if (mm_port_probe_list_is_xmm (probes)) { + mm_obj_dbg (self, "MBIM-powered XMM-based modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_xmm_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } + + mm_obj_dbg (self, "MBIM-powered Dell-branded modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + if (port_probe_list_has_manufacturer_port (probes, DELL_MANUFACTURER_NOVATEL)) { + mm_obj_dbg (self, "Novatel-powered Dell-branded modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_novatel_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } + + if (port_probe_list_has_manufacturer_port (probes, DELL_MANUFACTURER_SIERRA)) { + mm_obj_dbg (self, "Sierra-powered Dell-branded modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_sierra_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } + + if (port_probe_list_has_manufacturer_port (probes, DELL_MANUFACTURER_TELIT)) { + mm_obj_dbg (self, "Telit-powered Dell-branded modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_telit_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } + + if (mm_port_probe_list_is_xmm (probes)) { + mm_obj_dbg (self, "XMM-based modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_xmm_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } + + mm_obj_dbg (self, "Dell-branded generic modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +static gboolean +grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + if (MM_IS_BROADBAND_MODEM_SIERRA (modem)) + return mm_common_sierra_grab_port (self, modem, probe, error); + + if (MM_IS_BROADBAND_MODEM_TELIT (modem)) + return telit_grab_port (self, modem, probe, error); + + return mm_base_modem_grab_port (modem, + mm_port_probe_peek_port (probe), + mm_port_probe_get_port_type (probe), + MM_PORT_SERIAL_AT_FLAG_NONE, + error); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendors[] = { 0x413c, 0 }; + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (dell_custom_init), + .finish = G_CALLBACK (dell_custom_init_finish), + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_DELL, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendors, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + MM_PLUGIN_ALLOWED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + MM_PLUGIN_XMM_PROBE, TRUE, + NULL)); +} + +static void +mm_plugin_dell_init (MMPluginDell *self) +{ +} + +static void +mm_plugin_dell_class_init (MMPluginDellClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = grab_port; +} diff --git a/src/plugins/dell/mm-plugin-dell.h b/src/plugins/dell/mm-plugin-dell.h new file mode 100644 index 00000000..cc1a539e --- /dev/null +++ b/src/plugins/dell/mm-plugin-dell.h @@ -0,0 +1,46 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_DELL_H +#define MM_PLUGIN_DELL_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_DELL (mm_plugin_dell_get_type ()) +#define MM_PLUGIN_DELL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_DELL, MMPluginDell)) +#define MM_PLUGIN_DELL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_DELL, MMPluginDellClass)) +#define MM_IS_PLUGIN_DELL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_DELL)) +#define MM_IS_PLUGIN_DELL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_DELL)) +#define MM_PLUGIN_DELL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_DELL, MMPluginDellClass)) + +typedef struct { + MMPlugin parent; +} MMPluginDell; + +typedef struct { + MMPluginClass parent; +} MMPluginDellClass; + +GType mm_plugin_dell_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_DELL_H */ diff --git a/src/plugins/dlink/77-mm-dlink-port-types.rules b/src/plugins/dlink/77-mm-dlink-port-types.rules new file mode 100644 index 00000000..0dc7afce --- /dev/null +++ b/src/plugins/dlink/77-mm-dlink-port-types.rules @@ -0,0 +1,16 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_dlink_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2001", GOTO="mm_dlink_port_types" +GOTO="mm_dlink_port_types_end" + +LABEL="mm_dlink_port_types" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# D-Link DWM-222 +ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1" + +LABEL="mm_dlink_port_types_end" diff --git a/src/plugins/dlink/mm-plugin-dlink.c b/src/plugins/dlink/mm-plugin-dlink.c new file mode 100644 index 00000000..72476e2e --- /dev/null +++ b/src/plugins/dlink/mm-plugin-dlink.c @@ -0,0 +1,94 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-dlink.h" +#include "mm-broadband-modem.h" + +#if defined WITH_QMI +# include "mm-broadband-modem-qmi.h" +#endif + +G_DEFINE_TYPE (MMPluginDlink, mm_plugin_dlink, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered D-Link modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x2001, 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_DLINK, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + NULL)); +} + +static void +mm_plugin_dlink_init (MMPluginDlink *self) +{ +} + +static void +mm_plugin_dlink_class_init (MMPluginDlinkClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/dlink/mm-plugin-dlink.h b/src/plugins/dlink/mm-plugin-dlink.h new file mode 100644 index 00000000..13453cb0 --- /dev/null +++ b/src/plugins/dlink/mm-plugin-dlink.h @@ -0,0 +1,40 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_DLINK_H +#define MM_PLUGIN_DLINK_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_DLINK (mm_plugin_dlink_get_type ()) +#define MM_PLUGIN_DLINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_DLINK, MMPluginDlink)) +#define MM_PLUGIN_DLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_DLINK, MMPluginDlinkClass)) +#define MM_IS_PLUGIN_DLINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_DLINK)) +#define MM_IS_PLUGIN_DLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_DLINK)) +#define MM_PLUGIN_DLINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_DLINK, MMPluginDlinkClass)) + +typedef struct { + MMPlugin parent; +} MMPluginDlink; + +typedef struct { + MMPluginClass parent; +} MMPluginDlinkClass; + +GType mm_plugin_dlink_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_DLINK_H */ diff --git a/src/plugins/fibocom/77-mm-fibocom-port-types.rules b/src/plugins/fibocom/77-mm-fibocom-port-types.rules new file mode 100644 index 00000000..1fb26628 --- /dev/null +++ b/src/plugins/fibocom/77-mm-fibocom-port-types.rules @@ -0,0 +1,109 @@ +# do not edit this file, it will be overwritten on update +ACTION!="add|change|move|bind", GOTO="mm_fibocom_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2cb7", GOTO="mm_fibocom_port_types" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1782", GOTO="mm_fibocom_port_types" +GOTO="mm_fibocom_port_types_end" + +LABEL="mm_fibocom_port_types" + +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Fibocom L850-GL attach APN with toggle modem power +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0007", ENV{ID_MM_FIBOCOM_INITIAL_EPS_OFF_ON}="1" + +# Fibocom L850-GL +# ttyACM0 (if #2): AT port +# ttyACM1 (if #4): debug port (ignore) +# ttyACM2 (if #6): AT port +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0007", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" + +# Fibocom NL668-AM +# ttyACM0 (if #2): AT port +# ttyACM1 (if #3): AT port +# ttyACM2 (if #4): debug port (ignore) +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a0", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a0", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a0", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" + +# Fibocom FM150 +# ttyUSB0 (if #0): QCDM port +# ttyUSB1 (if #1): AT port +# ttyUSB2 (if #2): AT port +# ttyUSB2 (if #3): Ignore +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1" + +# Fibocom FM101-GL (MBIM) +# ttyUSB0 (if #2): AT port +# ttyUSB1 (if #3): AT port +# ttyUSB2 (if #4): debug port (ignore) +# ttyUSB3 (if #5): debug port (ignore) +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a2", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a2", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a2", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a2", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_IGNORE}="1" + +# Fibocom FM101-GL (ADB) +# ttyUSB0 (if #2): debug port (ignore) +# ttyUSB1 (if #3): AT port +# ttyUSB2 (if #4): debug port (ignore) +# ttyUSB3 (if #5): debug port (ignore) +# ttyUSB4 (if #6): debug port (ignore) +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" + + +# Fibocom MA510-GL (GTUSBMODE=31) +# ttyUSB0 (if #0): debug port (ignore) +# ttyUSB1 (if #1): AT port +# ttyUSB2 (if #2): AT port +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0106", ENV{ID_MM_FIBOCOM_INITIAL_EPS_CID}="1" + +# Fibocom MA510-GL (GTUSBMODE=32) +# ttyUSB1 (if #0): AT port +# ttyUSB2 (if #1): AT port +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="010a", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="010a", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="010a", ENV{ID_MM_FIBOCOM_INITIAL_EPS_CID}="1" + +# Fibocom L610 (GTUSBMODE=31) +# ttyUSB0 (if #0): AT port +# ttyUSB1 (if #1): NV +# ttyUSB2 (if #2): MOS +# ttyUSB3 (if #3): Diagnostic +# ttyUSB4 (if #4): Logging +# ttyUSB5 (if #5): AT port +# ttyUSB6 (if #6): AT port +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Fibocom L610 (GTUSBMODE={32,33} - ECM/RNDIS) +# ttyUSB0 (if #2): AT port +# ttyUSB1 (if #3): NV +# ttyUSB2 (if #4): MOS +# ttyUSB3 (if #5): Diagnostic +# ttyUSB4 (if #6): Logging +# ttyUSB5 (if #7): AT port +# ttyUSB6 (if #8): AT port +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="07", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="08", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +LABEL="mm_fibocom_port_types_end" diff --git a/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.c b/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.c new file mode 100644 index 00000000..bc4ee1ec --- /dev/null +++ b/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.c @@ -0,0 +1,540 @@ +/* -*- 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) 2022 Disruptive Technologies Research AS + */ + +#include <config.h> + +#include "mm-broadband-bearer-fibocom-ecm.h" +#include "mm-broadband-modem-fibocom.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-log.h" + +G_DEFINE_TYPE (MMBroadbandBearerFibocomEcm, mm_broadband_bearer_fibocom_ecm, MM_TYPE_BROADBAND_BEARER) + +/*****************************************************************************/ +/* Common helper functions */ + +static gboolean +parse_gtrndis_read_response (const gchar *response, + guint *state, + guint *cid, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + r = g_regex_new ("\\+GTRNDIS:\\s*(\\d+)(?:,(\\d+))?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match (r, response, 0, &match_info)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Invalid +GTRNDIS response: %s", response); + return FALSE; + } + if (!mm_get_uint_from_match_info (match_info, 1, state)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to match state in +GTRNDIS response: %s", response); + return FALSE; + } + + if (*state) { + if (!mm_get_uint_from_match_info (match_info, 2, cid)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to match cid in +GTRNDIS response: %s", response); + return FALSE; + } + } else { + *cid = 0; + } + + return TRUE; +} + +/*****************************************************************************/ +/* Connection status monitoring */ + +static MMBearerConnectionStatus +load_connection_status_finish (MMBaseBearer *bearer, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_BEARER_CONNECTION_STATUS_UNKNOWN; + } + return (MMBearerConnectionStatus) value; +} + +static void +gtrndis_query_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer; + GError *error = NULL; + const gchar *result; + guint state; + guint cid; + + bearer = g_task_get_source_object (task); + result = mm_base_modem_at_command_finish (modem, res, &error); + if (!result) + g_task_return_error (task, error); + else if (!parse_gtrndis_read_response (result, &state, &cid, &error)) + g_task_return_error (task, error); + else if (!state || (gint) cid != mm_base_bearer_get_profile_id (bearer)) + g_task_return_int (task, MM_BEARER_CONNECTION_STATUS_DISCONNECTED); + else + g_task_return_int (task, MM_BEARER_CONNECTION_STATUS_CONNECTED); + + g_object_unref (task); +} + +static void +load_connection_status (MMBaseBearer *bearer, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMBaseModem *modem = NULL; + + task = g_task_new (bearer, NULL, callback, user_data); + + g_object_get (MM_BASE_BEARER (bearer), + MM_BASE_BEARER_MODEM, &modem, + NULL); + + mm_base_modem_at_command (modem, + "+GTRNDIS?", + 3, + FALSE, + (GAsyncReadyCallback) gtrndis_query_ready, + task); + g_object_unref (modem); +} + +/*****************************************************************************/ +/* 3GPP Connect */ + +typedef struct { + MMBroadbandModem *modem; + MMPortSerialAt *primary; + MMPortSerialAt *secondary; + MMBearerIpFamily ip_family; +} ConnectContext; + +static void +connect_context_free (ConnectContext *ctx) +{ + g_clear_object (&ctx->modem); + g_clear_object (&ctx->primary); + g_clear_object (&ctx->secondary); + g_slice_free (ConnectContext, ctx); +} + +static MMBearerConnectResult * +connect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_connect_3gpp_ready (MMBroadbandBearer *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + MMBearerConnectResult *result; + + result = MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_fibocom_ecm_parent_class)->connect_3gpp_finish (self, res, &error); + if (result) + g_task_return_pointer (task, result, (GDestroyNotify) mm_bearer_connect_result_unref); + else + g_task_return_error (task, error); + g_object_unref (task); +} + +static void +disconnect_3gpp_ready (MMBroadbandBearer *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + gboolean result; + ConnectContext *ctx; + + result = MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp_finish (self, res, &error); + if (!result) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_fibocom_ecm_parent_class)->connect_3gpp ( + self, + ctx->modem, + ctx->primary, + ctx->secondary, + g_task_get_cancellable (task), + (GAsyncReadyCallback) parent_connect_3gpp_ready, + task); +} + +static void +gtrndis_check_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearer *self; + ConnectContext *ctx; + GError *error = NULL; + const gchar *response; + guint state; + guint cid; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!parse_gtrndis_read_response (response, &state, &cid, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (state) { + /* RNDIS is already active, disconnect first. */ + mm_obj_dbg (self, "RNDIS active, tearing down existing connection..."); + MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp ( + MM_BROADBAND_BEARER (self), + ctx->modem, + ctx->primary, + ctx->secondary, + NULL, /* data port */ + cid, + (GAsyncReadyCallback) disconnect_3gpp_ready, + task); + return; + } + + /* Execute the regular connection flow if RNDIS is inactive. */ + mm_obj_dbg (self, "RNDIS inactive"); + MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_fibocom_ecm_parent_class)->connect_3gpp ( + MM_BROADBAND_BEARER (self), + ctx->modem, + ctx->primary, + ctx->secondary, + g_task_get_cancellable (task), + (GAsyncReadyCallback) parent_connect_3gpp_ready, + task); +} + +static void +connect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ConnectContext *ctx; + GTask *task; + + ctx = g_slice_new0 (ConnectContext); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->secondary = secondary ? g_object_ref (secondary) : NULL; + ctx->ip_family = mm_bearer_properties_get_ip_type (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + mm_3gpp_normalize_ip_family (&ctx->ip_family); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify) connect_context_free); + + /* First, we must check whether RNDIS is already active */ + mm_base_modem_at_command (MM_BASE_MODEM (modem), + "+GTRNDIS?", + 3, + FALSE, /* allow_cached */ + (GAsyncReadyCallback) gtrndis_check_ready, + task); +} + +/*****************************************************************************/ +/* Dial context and task */ + +typedef struct { + MMBroadbandModem *modem; + MMPortSerialAt *primary; + guint cid; + MMPort *data; +} DialContext; + +static void +dial_task_free (DialContext *ctx) +{ + g_object_unref (ctx->modem); + g_object_unref (ctx->primary); + if (ctx->data) + g_object_unref (ctx->data); + g_slice_free (DialContext, ctx); +} + +static GTask * +dial_task_new (MMBroadbandBearerFibocomEcm *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DialContext *ctx; + GTask *task; + + ctx = g_slice_new0 (DialContext); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify) dial_task_free); + + ctx->data = mm_base_modem_get_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET); + if (!ctx->data) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to launch connection"); + g_object_unref (task); + return NULL; + } + + return task; +} + +/*****************************************************************************/ +/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ + +static MMPort * +dial_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +gtrndis_verify_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + DialContext *ctx; + GError *error = NULL; + const gchar *response; + + ctx = g_task_get_task_data (task); + response = mm_base_modem_at_command_finish (modem, res, &error); + + if (!response) + g_task_return_error (task, error); + else { + response = mm_strip_tag (response, "+GTRNDIS:"); + if (strtol (response, NULL, 10) != 1) + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Connection status verification failed"); + else + g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); + } + + g_object_unref (task); +} + +static void +gtrndis_activate_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_base_modem_at_command (modem, + "+GTRNDIS?", + 6, /* timeout [s] */ + FALSE, /* allow_cached */ + (GAsyncReadyCallback) gtrndis_verify_ready, + task); +} + +static void +dial_3gpp (MMBroadbandBearer *self, + MMBaseModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + g_autofree gchar *cmd = NULL; + + task = dial_task_new (MM_BROADBAND_BEARER_FIBOCOM_ECM (self), + MM_BROADBAND_MODEM (modem), + primary, + cid, + cancellable, + callback, + user_data); + if (!task) + return; + + cmd = g_strdup_printf ("+GTRNDIS=1,%u", cid); + mm_base_modem_at_command (modem, + cmd, + MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + FALSE, /* allow_cached */ + (GAsyncReadyCallback) gtrndis_activate_ready, + task); +} + +/*****************************************************************************/ +/* 3GPP Disconnect sequence */ + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gtrndis_deactivate_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (modem, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +disconnect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + g_autofree gchar *cmd = NULL; + + task = g_task_new (self, NULL, callback, user_data); + + cmd = g_strdup_printf ("+GTRNDIS=0,%u", cid); + mm_base_modem_at_command (MM_BASE_MODEM (modem), + cmd, + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, + FALSE, /* allow_cached */ + (GAsyncReadyCallback) gtrndis_deactivate_ready, + task); +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_broadband_bearer_fibocom_ecm_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *bearer; + GObject *source; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_fibocom_ecm_new (MMBroadbandModemFibocom *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + NULL); +} + +static void +mm_broadband_bearer_fibocom_ecm_init (MMBroadbandBearerFibocomEcm *self) +{ +} + +static void +mm_broadband_bearer_fibocom_ecm_class_init (MMBroadbandBearerFibocomEcmClass *klass) +{ + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + base_bearer_class->load_connection_status = load_connection_status; + base_bearer_class->load_connection_status_finish = load_connection_status_finish; + + broadband_bearer_class->connect_3gpp = connect_3gpp; + broadband_bearer_class->connect_3gpp_finish = connect_3gpp_finish; + broadband_bearer_class->dial_3gpp = dial_3gpp; + broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; +} diff --git a/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.h b/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.h new file mode 100644 index 00000000..ea367aeb --- /dev/null +++ b/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.h @@ -0,0 +1,50 @@ +/* -*- 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) 2022 Disruptive Technologies Research AS + */ + +#ifndef MM_BROADBAND_BEARER_FIBOCOM_ECM_H +#define MM_BROADBAND_BEARER_FIBOCOM_ECM_H + +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-fibocom.h" + +#define MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM (mm_broadband_bearer_fibocom_ecm_get_type ()) +#define MM_BROADBAND_BEARER_FIBOCOM_ECM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM, MMBroadbandBearerFibocomEcm)) +#define MM_BROADBAND_BEARER_FIBOCOM_ECM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM, MMBroadbandBearerFibocomEcmClass)) +#define MM_IS_BROADBAND_BEARER_FIBOCOM_ECM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM)) +#define MM_IS_BROADBAND_BEARER_FIBOCOM_ECM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM)) +#define MM_BROADBAND_BEARER_FIBOCOM_ECM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM, MMBroadbandBearerFibocomEcmClass)) + +typedef struct _MMBroadbandBearerFibocomEcm MMBroadbandBearerFibocomEcm; +typedef struct _MMBroadbandBearerFibocomEcmClass MMBroadbandBearerFibocomEcmClass; + +struct _MMBroadbandBearerFibocomEcm { + MMBroadbandBearer parent; +}; + +struct _MMBroadbandBearerFibocomEcmClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_fibocom_ecm_get_type (void); + +void mm_broadband_bearer_fibocom_ecm_new (MMBroadbandModemFibocom *modem, + MMBearerProperties *properties, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_fibocom_ecm_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_BROADBAND_BEARER_FIBOCOM_ECM_H */ diff --git a/src/plugins/fibocom/mm-broadband-modem-fibocom.c b/src/plugins/fibocom/mm-broadband-modem-fibocom.c new file mode 100644 index 00000000..9d659698 --- /dev/null +++ b/src/plugins/fibocom/mm-broadband-modem-fibocom.c @@ -0,0 +1,763 @@ +/* -*- 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) 2022 Disruptive Technologies Research AS + */ + +#include <config.h> + +#include "mm-broadband-modem-fibocom.h" +#include "mm-broadband-bearer-fibocom-ecm.h" +#include "mm-broadband-modem.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-3gpp-profile-manager.h" +#include "mm-log.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManager *iface); + +static MMIfaceModem3gppProfileManager *iface_modem_3gpp_profile_manager_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemFibocom, mm_broadband_modem_fibocom, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_PROFILE_MANAGER, iface_modem_3gpp_profile_manager_init)) + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED, +} FeatureSupport; + +struct _MMBroadbandModemFibocomPrivate { + FeatureSupport gtrndis_support; + GRegex *sim_ready_regex; + FeatureSupport initial_eps_bearer_support; + gint initial_eps_bearer_cid; +}; + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_fibocom_ecm_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_fibocom_ecm_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + g_object_unref (task); +} + +static void +broadband_bearer_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + g_object_unref (task); +} + +static void +common_create_bearer (GTask *task) +{ + MMBroadbandModemFibocom *self; + + self = g_task_get_source_object (task); + + switch (self->priv->gtrndis_support) { + case FEATURE_SUPPORTED: + mm_obj_dbg (self, "+GTRNDIS supported, creating Fibocom ECM bearer"); + mm_broadband_bearer_fibocom_ecm_new (self, + g_task_get_task_data (task), + NULL, /* cancellable */ + (GAsyncReadyCallback) broadband_bearer_fibocom_ecm_new_ready, + task); + return; + case FEATURE_NOT_SUPPORTED: + mm_obj_dbg (self, "+GTRNDIS not supported, creating generic PPP bearer"); + mm_broadband_bearer_new (MM_BROADBAND_MODEM (self), + g_task_get_task_data (task), + NULL, /* cancellable */ + (GAsyncReadyCallback) broadband_bearer_new_ready, + task); + return; + case FEATURE_SUPPORT_UNKNOWN: + default: + g_assert_not_reached (); + } +} + +static void +gtrndis_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self); + + if (!mm_base_modem_at_command_finish (_self, res, NULL)) { + mm_obj_dbg (self, "+GTRNDIS unsupported"); + self->priv->gtrndis_support = FEATURE_NOT_SUPPORTED; + } else { + mm_obj_dbg (self, "+GTRNDIS supported"); + self->priv->gtrndis_support = FEATURE_SUPPORTED; + } + + /* Go on and create the bearer */ + common_create_bearer (task); +} + +static void +modem_create_bearer (MMIfaceModem *_self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, g_object_ref (properties), g_object_unref); + + if (self->priv->gtrndis_support != FEATURE_SUPPORT_UNKNOWN) { + common_create_bearer (task); + return; + } + + if (!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) { + mm_obj_dbg (self, "skipping +GTRNDIS check as no data port is available"); + self->priv->gtrndis_support = FEATURE_NOT_SUPPORTED; + common_create_bearer (task); + return; + } + + mm_obj_dbg (self, "checking +GTRNDIS support..."); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+GTRNDIS=?", + 6, /* timeout [s] */ + TRUE, /* allow_cached */ + (GAsyncReadyCallback) gtrndis_test_ready, + task); +} + +/*****************************************************************************/ +/* Reset / Power (Modem interface) */ + +static gboolean +modem_common_power_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=15", + 15, + FALSE, + callback, + user_data); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=4", + 15, + FALSE, + callback, + user_data); +} + +static void +modem_power_off (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CPWROFF", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load initial EPS bearer properties (as agreed with network) */ + +static MMBearerProperties * +modem_3gpp_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error)); +} + +static void +load_initial_eps_cgcontrdp_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + g_autofree gchar *apn = NULL; + MMBearerProperties *properties; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response || !mm_3gpp_parse_cgcontrdp_response (response, NULL, NULL, &apn, NULL, NULL, NULL, NULL, NULL, &error)) + g_task_return_error (task, error); + else { + properties = mm_bearer_properties_new (); + mm_bearer_properties_set_apn (properties, apn); + g_task_return_pointer (task, properties, g_object_unref); + } + + g_object_unref (task); +} + +static void +modem_3gpp_load_initial_eps_bearer (MMIfaceModem3gpp *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self); + GTask *task; + g_autofree gchar *cmd = NULL; + + task = g_task_new (self, NULL, callback, user_data); + + if (self->priv->initial_eps_bearer_support != FEATURE_SUPPORTED) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Initial EPS bearer context ID unknown"); + g_object_unref (task); + return; + } + + g_assert (self->priv->initial_eps_bearer_cid >= 0); + cmd = g_strdup_printf ("+CGCONTRDP=%d", self->priv->initial_eps_bearer_cid); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 3, + FALSE, + (GAsyncReadyCallback) load_initial_eps_cgcontrdp_ready, + task); +} + +/*****************************************************************************/ +/* Load initial EPS bearer settings (currently configured in modem) */ + +static MMBearerProperties * +modem_3gpp_load_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error)); +} + +static void +load_initial_eps_bearer_get_profile_ready (MMIfaceModem3gppProfileManager *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + g_autoptr(MM3gppProfile) profile = NULL; + MMBearerProperties *properties; + + profile = mm_iface_modem_3gpp_profile_manager_get_profile_finish (self, res, &error); + if (!profile) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + properties = mm_bearer_properties_new_from_profile (profile, &error); + if (!properties) + g_task_return_error (task, error); + else + g_task_return_pointer (task, properties, g_object_unref); + g_object_unref (task); +} + +static void +modem_3gpp_load_initial_eps_bearer_settings (MMIfaceModem3gpp *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self); + MMPortSerialAt *port; + MMKernelDevice *device; + GTask *task; + + /* Initial EPS bearer CID initialization run once only */ + if (G_UNLIKELY (self->priv->initial_eps_bearer_support == FEATURE_SUPPORT_UNKNOWN)) { + /* There doesn't seem to be a programmatic way to find the initial EPS + * bearer's CID, so we'll use a udev variable. */ + port = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + device = mm_port_peek_kernel_device (MM_PORT (port)); + if (mm_kernel_device_has_global_property (device, "ID_MM_FIBOCOM_INITIAL_EPS_CID")) { + self->priv->initial_eps_bearer_support = FEATURE_SUPPORTED; + self->priv->initial_eps_bearer_cid = mm_kernel_device_get_global_property_as_int ( + device, "ID_MM_FIBOCOM_INITIAL_EPS_CID"); + } + else + self->priv->initial_eps_bearer_support = FEATURE_NOT_SUPPORTED; + } + + task = g_task_new (self, NULL, callback, user_data); + + if (self->priv->initial_eps_bearer_support != FEATURE_SUPPORTED) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Initial EPS bearer context ID unknown"); + g_object_unref (task); + return; + } + + g_assert (self->priv->initial_eps_bearer_cid >= 0); + mm_iface_modem_3gpp_profile_manager_get_profile ( + MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self), + self->priv->initial_eps_bearer_cid, + (GAsyncReadyCallback) load_initial_eps_bearer_get_profile_ready, + task); +} + +/*****************************************************************************/ +/* Set initial EPS bearer settings */ + +typedef enum { + SET_INITIAL_EPS_BEARER_SETTINGS_STEP_LOAD_POWER_STATE = 0, + SET_INITIAL_EPS_BEARER_SETTINGS_STEP_POWER_DOWN, + SET_INITIAL_EPS_BEARER_SETTINGS_STEP_MODIFY_PROFILE, + SET_INITIAL_EPS_BEARER_SETTINGS_STEP_POWER_UP, + SET_INITIAL_EPS_BEARER_SETTINGS_STEP_FINISH, +} SetInitialEpsStep; + +typedef struct { + MM3gppProfile *profile; + SetInitialEpsStep step; + MMModemPowerState power_state; +} SetInitialEpsContext; + +static void +set_initial_eps_context_free (SetInitialEpsContext *ctx) +{ + g_object_unref (ctx->profile); + g_slice_free (SetInitialEpsContext, ctx); +} + +static gboolean +modem_3gpp_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void set_initial_eps_step (GTask *task); + +static void +set_initial_eps_bearer_power_up_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self); + SetInitialEpsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_up_finish (MM_IFACE_MODEM (self), res, &error)) { + g_prefix_error (&error, "Couldn't power up modem: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx->step++; + set_initial_eps_step (task); +} + +static void +set_initial_eps_bearer_modify_profile_ready (MMIfaceModem3gppProfileManager *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + SetInitialEpsContext *ctx; + g_autoptr(MM3gppProfile) stored = NULL; + + ctx = g_task_get_task_data (task); + + stored = mm_iface_modem_3gpp_profile_manager_set_profile_finish (self, res, &error); + if (!stored) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx->step++; + set_initial_eps_step (task); +} + +static void +set_initial_eps_bearer_power_down_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + SetInitialEpsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_down_finish (MM_IFACE_MODEM (self), res, &error)) { + g_prefix_error (&error, "Couldn't power down modem: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx->step++; + set_initial_eps_step (task); +} + +static void +set_initial_eps_bearer_load_power_state_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + SetInitialEpsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + ctx->power_state = MM_IFACE_MODEM_GET_INTERFACE (self)->load_power_state_finish (MM_IFACE_MODEM (self), res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx->step++; + set_initial_eps_step (task); +} + +static void +set_initial_eps_step (GTask *task) +{ + MMBroadbandModemFibocom *self; + SetInitialEpsContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case SET_INITIAL_EPS_BEARER_SETTINGS_STEP_LOAD_POWER_STATE: + mm_obj_dbg (self, "querying current power state..."); + g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->load_power_state); + g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->load_power_state_finish); + MM_IFACE_MODEM_GET_INTERFACE (self)->load_power_state ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback) set_initial_eps_bearer_load_power_state_ready, + task); + return; + + case SET_INITIAL_EPS_BEARER_SETTINGS_STEP_POWER_DOWN: + if (ctx->power_state == MM_MODEM_POWER_STATE_ON) { + mm_obj_dbg (self, "powering down before changing initial EPS bearer settings..."); + g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_down); + g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_down_finish); + MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_down ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback) set_initial_eps_bearer_power_down_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case SET_INITIAL_EPS_BEARER_SETTINGS_STEP_MODIFY_PROFILE: + mm_obj_dbg (self, "modifying initial EPS bearer settings profile..."); + mm_iface_modem_3gpp_profile_manager_set_profile (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self), + ctx->profile, + "profile-id", + TRUE, + (GAsyncReadyCallback) set_initial_eps_bearer_modify_profile_ready, + task); + return; + + case SET_INITIAL_EPS_BEARER_SETTINGS_STEP_POWER_UP: + if (ctx->power_state == MM_MODEM_POWER_STATE_ON) { + mm_obj_dbg (self, "powering up after changing initial EPS bearer settings..."); + g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_up); + g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_up_finish); + MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_up ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback) set_initial_eps_bearer_power_up_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case SET_INITIAL_EPS_BEARER_SETTINGS_STEP_FINISH: + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +modem_3gpp_set_initial_eps_bearer_settings (MMIfaceModem3gpp *_self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self); + GTask *task; + MM3gppProfile *profile; + MMBearerIpFamily ip_family; + SetInitialEpsContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + + if (self->priv->initial_eps_bearer_support != FEATURE_SUPPORTED) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Initial EPS bearer context ID unknown"); + g_object_unref (task); + return; + } + + profile = mm_bearer_properties_peek_3gpp_profile (properties); + g_assert (self->priv->initial_eps_bearer_cid >= 0); + mm_3gpp_profile_set_profile_id (profile, self->priv->initial_eps_bearer_cid); + ip_family = mm_3gpp_profile_get_ip_type (profile); + if (ip_family == MM_BEARER_IP_FAMILY_NONE || ip_family == MM_BEARER_IP_FAMILY_ANY) + mm_3gpp_profile_set_ip_type (profile, MM_BEARER_IP_FAMILY_IPV4); + + /* Setup context */ + ctx = g_slice_new0 (SetInitialEpsContext); + ctx->profile = g_object_ref (profile); + ctx->step = SET_INITIAL_EPS_BEARER_SETTINGS_STEP_LOAD_POWER_STATE; + g_task_set_task_data (task, ctx, (GDestroyNotify) set_initial_eps_context_free); + + set_initial_eps_step (task); +} + +/*****************************************************************************/ +/* Deactivate profile (3GPP profile management interface) */ + +static gboolean +modem_3gpp_profile_manager_deactivate_profile_finish (MMIfaceModem3gppProfileManager *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +profile_manager_parent_deactivate_profile_ready (MMIfaceModem3gppProfileManager *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + if (iface_modem_3gpp_profile_manager_parent->deactivate_profile_finish(self, res, &error)) + g_task_return_boolean (task, TRUE); + else + g_task_return_error (task, error); + g_object_unref (task); +} + +static void +modem_3gpp_profile_manager_deactivate_profile (MMIfaceModem3gppProfileManager *_self, + MM3gppProfile *profile, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self); + GTask *task; + gint profile_id; + + task = g_task_new (self, NULL, callback, user_data); + profile_id = mm_3gpp_profile_get_profile_id (profile); + + if (self->priv->initial_eps_bearer_support == FEATURE_SUPPORTED) { + g_assert (self->priv->initial_eps_bearer_cid >= 0); + if (self->priv->initial_eps_bearer_cid == profile_id) { + mm_obj_dbg (self, "skipping profile deactivation (initial EPS bearer)"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + } + + iface_modem_3gpp_profile_manager_parent->deactivate_profile ( + _self, + profile, + (GAsyncReadyCallback) profile_manager_parent_deactivate_profile_ready, + task); +} + +/*****************************************************************************/ + +static void +setup_ports (MMBroadbandModem *_self) +{ + MMBroadbandModemFibocom *self = (MM_BROADBAND_MODEM_FIBOCOM (_self)); + MMPortSerialAt *ports[2]; + guint i; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_fibocom_parent_class)->setup_ports (_self); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->sim_ready_regex, + NULL, NULL, NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemFibocom * +mm_broadband_modem_fibocom_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_FIBOCOM, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_fibocom_init (MMBroadbandModemFibocom *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_FIBOCOM, + MMBroadbandModemFibocomPrivate); + + self->priv->gtrndis_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->sim_ready_regex = g_regex_new ("\\r\\n\\+SIM READY\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->initial_eps_bearer_support = FEATURE_SUPPORT_UNKNOWN; +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (object); + + g_regex_unref (self->priv->sim_ready_regex); + + G_OBJECT_CLASS (mm_broadband_modem_fibocom_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; + iface->reset = modem_reset; + iface->reset_finish = modem_common_power_finish; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_common_power_finish; + iface->modem_power_off = modem_power_off; + iface->modem_power_off_finish = modem_common_power_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface->load_initial_eps_bearer = modem_3gpp_load_initial_eps_bearer; + iface->load_initial_eps_bearer_finish = modem_3gpp_load_initial_eps_bearer_finish; + iface->load_initial_eps_bearer_settings = modem_3gpp_load_initial_eps_bearer_settings; + iface->load_initial_eps_bearer_settings_finish = modem_3gpp_load_initial_eps_bearer_settings_finish; + iface->set_initial_eps_bearer_settings = modem_3gpp_set_initial_eps_bearer_settings; + iface->set_initial_eps_bearer_settings_finish = modem_3gpp_set_initial_eps_bearer_settings_finish; +} + +static void +iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManager *iface) +{ + iface_modem_3gpp_profile_manager_parent = g_type_interface_peek_parent (iface); + + iface->deactivate_profile = modem_3gpp_profile_manager_deactivate_profile; + iface->deactivate_profile_finish = modem_3gpp_profile_manager_deactivate_profile_finish; +} + +static void +mm_broadband_modem_fibocom_class_init (MMBroadbandModemFibocomClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (G_OBJECT_CLASS (klass), + sizeof (MMBroadbandModemFibocomPrivate)); + + /* Virtual methods */ + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/fibocom/mm-broadband-modem-fibocom.h b/src/plugins/fibocom/mm-broadband-modem-fibocom.h new file mode 100644 index 00000000..958841b7 --- /dev/null +++ b/src/plugins/fibocom/mm-broadband-modem-fibocom.h @@ -0,0 +1,49 @@ +/* -*- 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) 2022 Disruptive Technologies Research AS + */ + +#ifndef MM_BROADBAND_MODEM_FIBOCOM_H +#define MM_BROADBAND_MODEM_FIBOCOM_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_FIBOCOM (mm_broadband_modem_fibocom_get_type ()) +#define MM_BROADBAND_MODEM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_FIBOCOM, MMBroadbandModemFibocom)) +#define MM_BROADBAND_MODEM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_FIBOCOM, MMBroadbandModemFibocomClass)) +#define MM_IS_BROADBAND_MODEM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_FIBOCOM)) +#define MM_IS_BROADBAND_MODEM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_FIBOCOM)) +#define MM_BROADBAND_MODEM_FIBOCOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_FIBOCOM, MMBroadbandModemFibocomClass)) + +typedef struct _MMBroadbandModemFibocom MMBroadbandModemFibocom; +typedef struct _MMBroadbandModemFibocomClass MMBroadbandModemFibocomClass; +typedef struct _MMBroadbandModemFibocomPrivate MMBroadbandModemFibocomPrivate; + +struct _MMBroadbandModemFibocom { + MMBroadbandModem parent; + MMBroadbandModemFibocomPrivate *priv; +}; + +struct _MMBroadbandModemFibocomClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_fibocom_get_type (void); + +MMBroadbandModemFibocom *mm_broadband_modem_fibocom_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_FIBOCOM_H */ diff --git a/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.c b/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.c new file mode 100644 index 00000000..b4655db7 --- /dev/null +++ b/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.c @@ -0,0 +1,95 @@ +/* -*- 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) 2022 Fibocom Wireless Inc. + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-broadband-modem-mbim-fibocom.h" +#include "mm-shared-fibocom.h" + +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void shared_fibocom_init (MMSharedFibocom *iface); + +static MMIfaceModem3gpp *iface_modem_3gpp_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimFibocom, mm_broadband_modem_mbim_fibocom, MM_TYPE_BROADBAND_MODEM_MBIM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_FIBOCOM, shared_fibocom_init)) + +/******************************************************************************/ + +MMBroadbandModemMbimFibocom * +mm_broadband_modem_mbim_fibocom_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* MBIM bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE, +#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED + MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED, TRUE, +#endif + NULL); +} + +static void +mm_broadband_modem_mbim_fibocom_init (MMBroadbandModemMbimFibocom *self) +{ +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->set_initial_eps_bearer_settings = mm_shared_fibocom_set_initial_eps_bearer_settings; + iface->set_initial_eps_bearer_settings_finish = mm_shared_fibocom_set_initial_eps_bearer_settings_finish; +} + +static MMIfaceModem3gpp * +peek_parent_3gpp_interface (MMSharedFibocom *self) +{ + return iface_modem_3gpp_parent; +} + +static void +shared_fibocom_init (MMSharedFibocom *iface) +{ + iface->peek_parent_3gpp_interface = peek_parent_3gpp_interface; +} + +static void +mm_broadband_modem_mbim_fibocom_class_init (MMBroadbandModemMbimFibocomClass *klass) +{ +} diff --git a/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.h b/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.h new file mode 100644 index 00000000..b5c5434f --- /dev/null +++ b/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.h @@ -0,0 +1,47 @@ +/* -*- 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) 2022 Fibocom Wireless Inc. + */ + +#ifndef MM_BROADBAND_MODEM_MBIM_FIBOCOM_H +#define MM_BROADBAND_MODEM_MBIM_FIBOCOM_H + +#include "mm-broadband-modem-mbim.h" + +#define MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM (mm_broadband_modem_mbim_fibocom_get_type ()) +#define MM_BROADBAND_MODEM_MBIM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM, MMBroadbandModemMbimFibocom)) +#define MM_BROADBAND_MODEM_MBIM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM, MMBroadbandModemMbimFibocomClass)) +#define MM_IS_BROADBAND_MODEM_MBIM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM)) +#define MM_IS_BROADBAND_MODEM_MBIM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM)) +#define MM_BROADBAND_MODEM_MBIM_FIBOCOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM, MMBroadbandModemMbimFibocomClass)) + +typedef struct _MMBroadbandModemMbimFibocom MMBroadbandModemMbimFibocom; +typedef struct _MMBroadbandModemMbimFibocomClass MMBroadbandModemMbimFibocomClass; + +struct _MMBroadbandModemMbimFibocom { + MMBroadbandModemMbim parent; +}; + +struct _MMBroadbandModemMbimFibocomClass{ + MMBroadbandModemMbimClass parent; +}; + +GType mm_broadband_modem_mbim_fibocom_get_type (void); + +MMBroadbandModemMbimFibocom *mm_broadband_modem_mbim_fibocom_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_MBIM_FIBOCOM_H */ diff --git a/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.c b/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.c new file mode 100644 index 00000000..7ed39362 --- /dev/null +++ b/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.c @@ -0,0 +1,95 @@ +/* -*- 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) 2022 Fibocom Wireless Inc. + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-broadband-modem-mbim-xmm-fibocom.h" +#include "mm-shared-fibocom.h" + +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void shared_fibocom_init (MMSharedFibocom *iface); + +static MMIfaceModem3gpp *iface_modem_3gpp_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimXmmFibocom, mm_broadband_modem_mbim_xmm_fibocom, MM_TYPE_BROADBAND_MODEM_MBIM_XMM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_FIBOCOM, shared_fibocom_init)) + +/******************************************************************************/ + +MMBroadbandModemMbimXmmFibocom * +mm_broadband_modem_mbim_xmm_fibocom_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* MBIM bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE, +#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED + MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED, TRUE, +#endif + NULL); +} + +static void +mm_broadband_modem_mbim_xmm_fibocom_init (MMBroadbandModemMbimXmmFibocom *self) +{ +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->set_initial_eps_bearer_settings = mm_shared_fibocom_set_initial_eps_bearer_settings; + iface->set_initial_eps_bearer_settings_finish = mm_shared_fibocom_set_initial_eps_bearer_settings_finish; +} + +static MMIfaceModem3gpp * +peek_parent_3gpp_interface (MMSharedFibocom *self) +{ + return iface_modem_3gpp_parent; +} + +static void +shared_fibocom_init (MMSharedFibocom *iface) +{ + iface->peek_parent_3gpp_interface = peek_parent_3gpp_interface; +} + +static void +mm_broadband_modem_mbim_xmm_fibocom_class_init (MMBroadbandModemMbimXmmFibocomClass *klass) +{ +} diff --git a/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.h b/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.h new file mode 100644 index 00000000..db51cfc8 --- /dev/null +++ b/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.h @@ -0,0 +1,47 @@ +/* -*- 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) 2022 Fibocom Wireless Inc. + */ + +#ifndef MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_H +#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_H + +#include "mm-broadband-modem-mbim-xmm.h" + +#define MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM (mm_broadband_modem_mbim_xmm_fibocom_get_type ()) +#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM, MMBroadbandModemMbimXmmFibocom)) +#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM, MMBroadbandModemMbimXmmFibocomClass)) +#define MM_IS_BROADBAND_MODEM_MBIM_XMM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM)) +#define MM_IS_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM)) +#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM, MMBroadbandModemMbimXmmFibocomClass)) + +typedef struct _MMBroadbandModemMbimXmmFibocom MMBroadbandModemMbimXmmFibocom; +typedef struct _MMBroadbandModemMbimXmmFibocomClass MMBroadbandModemMbimXmmFibocomClass; + +struct _MMBroadbandModemMbimXmmFibocom { + MMBroadbandModemMbimXmm parent; +}; + +struct _MMBroadbandModemMbimXmmFibocomClass{ + MMBroadbandModemMbimXmmClass parent; +}; + +GType mm_broadband_modem_mbim_xmm_fibocom_get_type (void); + +MMBroadbandModemMbimXmmFibocom *mm_broadband_modem_mbim_xmm_fibocom_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_H */ diff --git a/src/plugins/fibocom/mm-plugin-fibocom.c b/src/plugins/fibocom/mm-plugin-fibocom.c new file mode 100644 index 00000000..4ef4d483 --- /dev/null +++ b/src/plugins/fibocom/mm-plugin-fibocom.c @@ -0,0 +1,136 @@ +/* -*- 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) 2018-2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <stdlib.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-fibocom.h" +#include "mm-broadband-modem.h" +#include "mm-broadband-modem-xmm.h" +#include "mm-broadband-modem-fibocom.h" + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#include "mm-broadband-modem-mbim-fibocom.h" +#include "mm-broadband-modem-mbim-xmm.h" +#include "mm-broadband-modem-mbim-xmm-fibocom.h" +#endif + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi.h" +#endif + +G_DEFINE_TYPE (MMPluginFibocom, mm_plugin_fibocom, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + if (mm_port_probe_list_is_xmm (probes)) { + mm_obj_dbg (self, "MBIM-powered XMM-based Fibocom modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_xmm_fibocom_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } + mm_obj_dbg (self, "MBIM-powered Fibocom modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_fibocom_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Fibocom modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + if (mm_port_probe_list_is_xmm (probes)) { + mm_obj_dbg (self, "XMM-based Fibocom modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_xmm_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } + + mm_obj_dbg (self, "Fibocom modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_fibocom_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x2cb7, 0x1782, 0 }; + static const gchar *drivers[] = { "cdc_mbim", "qmi_wwan", "cdc_ether", "option", NULL }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_FIBOCOM, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_DRIVERS, drivers, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_XMM_PROBE, TRUE, + NULL)); +} + +static void +mm_plugin_fibocom_init (MMPluginFibocom *self) +{ +} + +static void +mm_plugin_fibocom_class_init (MMPluginFibocomClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/fibocom/mm-plugin-fibocom.h b/src/plugins/fibocom/mm-plugin-fibocom.h new file mode 100644 index 00000000..e5289979 --- /dev/null +++ b/src/plugins/fibocom/mm-plugin-fibocom.h @@ -0,0 +1,40 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_FIBOCOM_H +#define MM_PLUGIN_FIBOCOM_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_FIBOCOM (mm_plugin_fibocom_get_type ()) +#define MM_PLUGIN_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_FIBOCOM, MMPluginFibocom)) +#define MM_PLUGIN_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_FIBOCOM, MMPluginFibocomClass)) +#define MM_IS_PLUGIN_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_FIBOCOM)) +#define MM_IS_PLUGIN_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_FIBOCOM)) +#define MM_PLUGIN_FIBOCOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_FIBOCOM, MMPluginFibocomClass)) + +typedef struct { + MMPlugin parent; +} MMPluginFibocom; + +typedef struct { + MMPluginClass parent; +} MMPluginFibocomClass; + +GType mm_plugin_fibocom_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_FIBOCOM_H */ diff --git a/src/plugins/fibocom/mm-shared-fibocom.c b/src/plugins/fibocom/mm-shared-fibocom.c new file mode 100644 index 00000000..10b82c59 --- /dev/null +++ b/src/plugins/fibocom/mm-shared-fibocom.c @@ -0,0 +1,246 @@ +/* -*- 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) 2022 Fibocom Wireless Inc. + */ + +#include <config.h> +#include <arpa/inet.h> + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-broadband-modem.h" +#include "mm-broadband-modem-mbim.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-shared-fibocom.h" + +/*****************************************************************************/ +/* Private data context */ + +#define PRIVATE_TAG "shared-intel-private-tag" +static GQuark private_quark; + +typedef struct { + /* 3GPP interface support */ + MMIfaceModem3gpp *iface_modem_3gpp_parent; +} Private; + +static void +private_free (Private *priv) +{ + g_slice_free (Private, priv); +} + +static Private * +get_private (MMSharedFibocom *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); + + /* Setup parent class' MMIfaceModem3gpp */ + g_assert (MM_SHARED_FIBOCOM_GET_INTERFACE (self)->peek_parent_3gpp_interface); + priv->iface_modem_3gpp_parent = MM_SHARED_FIBOCOM_GET_INTERFACE (self)->peek_parent_3gpp_interface (self); + + g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); + } + + return priv; +} + +/*****************************************************************************/ + +typedef struct { + MMBearerProperties *config; + gboolean initial_eps_off_on; +} SetInitialEpsBearerSettingsContext; + +static void +set_initial_eps_bearer_settings_context_free (SetInitialEpsBearerSettingsContext *ctx) +{ + g_clear_object (&ctx->config); + g_slice_free (SetInitialEpsBearerSettingsContext, ctx); +} + +gboolean +mm_shared_fibocom_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +after_attach_apn_modem_power_up_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_iface_modem_set_power_state_finish (self, res, &error)) { + mm_obj_warn (self, "failed to power up modem after attach APN settings update: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "success toggling modem power up after attach APN"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_set_initial_eps_bearer_settings_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + SetInitialEpsBearerSettingsContext *ctx; + Private *priv; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + priv = get_private (MM_SHARED_FIBOCOM (self)); + + if (!priv->iface_modem_3gpp_parent->set_initial_eps_bearer_settings_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (ctx->initial_eps_off_on) { + mm_obj_dbg (self, "toggle modem power up after attach APN"); + mm_iface_modem_set_power_state (MM_IFACE_MODEM (self), + MM_MODEM_POWER_STATE_ON, + (GAsyncReadyCallback) after_attach_apn_modem_power_up_ready, + task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_set_initial_eps_bearer_settings (GTask *task) +{ + MMSharedFibocom *self; + SetInitialEpsBearerSettingsContext *ctx; + Private *priv; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + priv = get_private (self); + + g_assert (priv->iface_modem_3gpp_parent); + g_assert (priv->iface_modem_3gpp_parent->set_initial_eps_bearer_settings); + g_assert (priv->iface_modem_3gpp_parent->set_initial_eps_bearer_settings_finish); + + priv->iface_modem_3gpp_parent->set_initial_eps_bearer_settings (MM_IFACE_MODEM_3GPP (self), + ctx->config, + (GAsyncReadyCallback)parent_set_initial_eps_bearer_settings_ready, + task); +} + +static void +before_attach_apn_modem_power_down_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_iface_modem_set_power_state_finish (self, res, &error)) { + mm_obj_warn (self, "failed to power down modem before attach APN settings update: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + mm_obj_dbg (self, "success toggling modem power down before attach APN"); + + parent_set_initial_eps_bearer_settings (task); +} + +void +mm_shared_fibocom_set_initial_eps_bearer_settings (MMIfaceModem3gpp *self, + MMBearerProperties *config, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SetInitialEpsBearerSettingsContext *ctx; + GTask *task; + MMPortMbim *port; + + task = g_task_new (self, NULL, callback, user_data); + + /* This shared logic is only expected in MBIM capable devices */ + g_assert (MM_IS_BROADBAND_MODEM_MBIM (self)); + port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self)); + if (!port) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No valid MBIM port found"); + g_object_unref (task); + return; + } + + ctx = g_slice_new0 (SetInitialEpsBearerSettingsContext); + ctx->config = g_object_ref (config); + ctx->initial_eps_off_on = mm_kernel_device_get_property_as_boolean (mm_port_peek_kernel_device (MM_PORT (port)), "ID_MM_FIBOCOM_INITIAL_EPS_OFF_ON"); + g_task_set_task_data (task, ctx, (GDestroyNotify)set_initial_eps_bearer_settings_context_free); + + if (ctx->initial_eps_off_on) { + mm_obj_dbg (self, "toggle modem power down before attach APN"); + mm_iface_modem_set_power_state (MM_IFACE_MODEM (self), + MM_MODEM_POWER_STATE_LOW, + (GAsyncReadyCallback) before_attach_apn_modem_power_down_ready, + task); + return; + } + + parent_set_initial_eps_bearer_settings (task); +} + +/*****************************************************************************/ + +static void +shared_fibocom_init (gpointer g_iface) +{ +} + +GType +mm_shared_fibocom_get_type (void) +{ + static GType shared_fibocom_type = 0; + + if (!G_UNLIKELY (shared_fibocom_type)) { + static const GTypeInfo info = { + sizeof (MMSharedFibocom), /* class_size */ + shared_fibocom_init, /* base_init */ + NULL, /* base_finalize */ + }; + + shared_fibocom_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedFibocom", &info, 0); + g_type_interface_add_prerequisite (shared_fibocom_type, MM_TYPE_IFACE_MODEM); + g_type_interface_add_prerequisite (shared_fibocom_type, MM_TYPE_IFACE_MODEM_3GPP); + } + + return shared_fibocom_type; +} diff --git a/src/plugins/fibocom/mm-shared-fibocom.h b/src/plugins/fibocom/mm-shared-fibocom.h new file mode 100644 index 00000000..cc4348d2 --- /dev/null +++ b/src/plugins/fibocom/mm-shared-fibocom.h @@ -0,0 +1,53 @@ +/* -*- 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) 2022 Fibocom Wireless Inc. + */ + +#ifndef MM_SHARED_FIBOCOM_H +#define MM_SHARED_FIBOCOM_H + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem.h" + +#define MM_TYPE_SHARED_FIBOCOM (mm_shared_fibocom_get_type ()) +#define MM_SHARED_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_FIBOCOM, MMSharedFibocom)) +#define MM_IS_SHARED_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_FIBOCOM)) +#define MM_SHARED_FIBOCOM_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_FIBOCOM, MMSharedFibocom)) + +typedef struct _MMSharedFibocom MMSharedFibocom; + +struct _MMSharedFibocom { + GTypeInterface g_iface; + + /* Peek 3GPP interface of the parent class of the object */ + MMIfaceModem3gpp * (* peek_parent_3gpp_interface) (MMSharedFibocom *self); +}; + +GType mm_shared_fibocom_get_type (void); + +void mm_shared_fibocom_set_initial_eps_bearer_settings (MMIfaceModem3gpp *self, + MMBearerProperties *config, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_fibocom_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error); + +#endif /* MM_SHARED_FIBOCOM_H */ diff --git a/src/plugins/fibocom/mm-shared.c b/src/plugins/fibocom/mm-shared.c new file mode 100644 index 00000000..b99bc3aa --- /dev/null +++ b/src/plugins/fibocom/mm-shared.c @@ -0,0 +1,20 @@ +/* -*- 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) 2022 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-shared.h" + +MM_SHARED_DEFINE_MAJOR_VERSION +MM_SHARED_DEFINE_MINOR_VERSION +MM_SHARED_DEFINE_NAME(Intel) diff --git a/src/plugins/foxconn/77-mm-foxconn-port-types.rules b/src/plugins/foxconn/77-mm-foxconn-port-types.rules new file mode 100644 index 00000000..344df152 --- /dev/null +++ b/src/plugins/foxconn/77-mm-foxconn-port-types.rules @@ -0,0 +1,26 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_foxconn_port_types_end" + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0489", GOTO="mm_foxconn_vendorcheck" +GOTO="mm_foxconn_port_types_end" + +LABEL="mm_foxconn_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Foxconn T77w968 (default 0xe0b4, with esim support 0xe0b5) +# if 02: primary port +# if 03: secondary port +# if 04: raw NMEA port +# if 05: diag/qcdm port +ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b4", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b4", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b4", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b4", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b5", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b5", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b5", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b5", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" + +GOTO="mm_foxconn_port_types_end" +LABEL="mm_foxconn_port_types_end" diff --git a/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.c b/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.c new file mode 100644 index 00000000..cec1c617 --- /dev/null +++ b/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.c @@ -0,0 +1,612 @@ +/* -*- 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) 2018-2019 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <time.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-errors-types.h" +#include "mm-modem-helpers.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" +#include "mm-broadband-modem-mbim-foxconn.h" + +#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED +# include "mm-iface-modem-firmware.h" +# include "mm-shared-qmi.h" +# include "mm-log.h" +#endif + +static void iface_modem_location_init (MMIfaceModemLocation *iface); + +#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED +static void iface_modem_firmware_init (MMIfaceModemFirmware *iface); +#endif + +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimFoxconn, mm_broadband_modem_mbim_foxconn, MM_TYPE_BROADBAND_MODEM_MBIM, 0, +#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init) +#endif + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)) + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED +} FeatureSupport; + +struct _MMBroadbandModemMbimFoxconnPrivate { + FeatureSupport unmanaged_gps_support; +}; + + +#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED + +/*****************************************************************************/ +/* Firmware update settings + * + * We only support reporting firmware update settings when QMI support is built, + * because this is the only clean way to get the expected firmware version to + * report. + */ + +static MMFirmwareUpdateSettings * +firmware_load_update_settings_finish (MMIfaceModemFirmware *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static gboolean +needs_qdu_and_mcfg_apps_version (MMIfaceModemFirmware *self) +{ + guint vendor_id; + guint product_id; + + /* 0x105b is the T99W175 module, T99W175 supports QDU and requires MCFG+APPS version. + * T99W265(0x0489:0xe0da ; 0x0489:0xe0db): supports QDU and requires MCFG+APPS version. + * else support FASTBOOT and QMI PDC, and require only MCFG version. + */ + vendor_id = mm_base_modem_get_vendor_id (MM_BASE_MODEM (self)); + product_id = mm_base_modem_get_product_id (MM_BASE_MODEM (self)); + return (vendor_id == 0x105b || (vendor_id == 0x0489 && (product_id == 0xe0da || product_id == 0xe0db))); +} + +/*****************************************************************************/ +/* Need APPS version for the development of different functions when T77W968 support FASTBOOT and QMI PDC. + * Such as: T77W968.F1.0.0.5.2.GC.013.037 and T77W968.F1.0.0.5.2.GC.013.049, the MCFG version(T77W968.F1.0.0.5.2.GC.013) is same, + * but the APPS version(037 and 049) is different. + * + * For T77W968.F1.0.0.5.2.GC.013.049, before the change, "fwupdmgr get-devices" can obtain Current version is T77W968.F1.0.0.5.2.GC.013, + * it only include the MCFG version. + * After add need APPS version, it shows Current version is T77W968.F1.0.0.5.2.GC.013.049, including the MCFG+APPS version. + */ + +static gboolean +needs_fastboot_and_qmi_pdc_mcfg_apps_version (MMIfaceModemFirmware *self) +{ + guint vendor_id; + guint product_id; + + /* T77W968(0x413c:0x81d7 ; 0x413c:0x81e0 ; 0x413c:0x81e4 ; 0x413c:0x81e6): supports FASTBOOT and QMI PDC, + * and requires MCFG+APPS version. + * else support FASTBOOT and QMI PDC, and require only MCFG version. + */ + vendor_id = mm_base_modem_get_vendor_id (MM_BASE_MODEM (self)); + product_id = mm_base_modem_get_product_id (MM_BASE_MODEM (self)); + return (vendor_id == 0x413c && (product_id == 0x81d7 || product_id == 0x81e0 || product_id == 0x81e4 || product_id == 0x81e6)); +} + +static MMFirmwareUpdateSettings * +create_update_settings (MMIfaceModemFirmware *self, + const gchar *version_str) +{ + MMModemFirmwareUpdateMethod methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE; + MMFirmwareUpdateSettings *update_settings = NULL; + + if (needs_qdu_and_mcfg_apps_version (self)) + methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU; + else + methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT | MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC; + + update_settings = mm_firmware_update_settings_new (methods); + if (methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) + mm_firmware_update_settings_set_fastboot_at (update_settings, "AT^FASTBOOT"); + mm_firmware_update_settings_set_version (update_settings, version_str); + return update_settings; +} + +static void +dms_foxconn_get_firmware_version_ready (QmiClientDms *client, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(QmiMessageDmsFoxconnGetFirmwareVersionOutput) output = NULL; + GError *error = NULL; + const gchar *str; + + output = qmi_client_dms_foxconn_get_firmware_version_finish (client, res, &error); + if (!output || !qmi_message_dms_foxconn_get_firmware_version_output_get_result (output, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + qmi_message_dms_foxconn_get_firmware_version_output_get_version (output, &str, NULL); + + g_task_return_pointer (task, + create_update_settings (g_task_get_source_object (task), str), + g_object_unref); + g_object_unref (task); +} + +static void +fox_get_firmware_version_ready (QmiClientFox *client, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(QmiMessageFoxGetFirmwareVersionOutput) output = NULL; + GError *error = NULL; + const gchar *str; + + output = qmi_client_fox_get_firmware_version_finish (client, res, &error); + if (!output || !qmi_message_fox_get_firmware_version_output_get_result (output, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + qmi_message_fox_get_firmware_version_output_get_version (output, &str, NULL); + + g_task_return_pointer (task, + create_update_settings (g_task_get_source_object (task), str), + g_object_unref); + g_object_unref (task); +} + +static void +mbim_port_allocate_qmi_client_ready (MMPortMbim *mbim, + GAsyncResult *res, + GTask *task) +{ + MMIfaceModemFirmware *self; + QmiClient *fox_client = NULL; + QmiClient *dms_client = NULL; + g_autoptr(GError) error = NULL; + + self = g_task_get_source_object (task); + + if (!mm_port_mbim_allocate_qmi_client_finish (mbim, res, &error)) + mm_obj_dbg (self, "Allocate FOX client failed: %s", error->message); + + /* Try to get firmware version over fox service, if it failed to peek client, try dms service. */ + fox_client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_FOX, MM_PORT_QMI_FLAG_DEFAULT, NULL); + if (!fox_client) { + dms_client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_DMS, MM_PORT_QMI_FLAG_DEFAULT, NULL); + if (!dms_client) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unable to load version info: no FOX or DMS client available"); + g_object_unref (task); + return; + } + } + + if (fox_client) { + g_autoptr(QmiMessageFoxGetFirmwareVersionInput) input = NULL; + + input = qmi_message_fox_get_firmware_version_input_new (); + qmi_message_fox_get_firmware_version_input_set_version_type (input, + (needs_qdu_and_mcfg_apps_version (self) ? + QMI_FOX_FIRMWARE_VERSION_TYPE_FIRMWARE_MCFG_APPS : + QMI_FOX_FIRMWARE_VERSION_TYPE_FIRMWARE_MCFG), + NULL); + qmi_client_fox_get_firmware_version (QMI_CLIENT_FOX (fox_client), + input, + 10, + NULL, + (GAsyncReadyCallback)fox_get_firmware_version_ready, + task); + return; + } + + if (dms_client) { + g_autoptr(QmiMessageDmsFoxconnGetFirmwareVersionInput) input = NULL; + + input = qmi_message_dms_foxconn_get_firmware_version_input_new (); + qmi_message_dms_foxconn_get_firmware_version_input_set_version_type (input, + ((needs_qdu_and_mcfg_apps_version (self) || needs_fastboot_and_qmi_pdc_mcfg_apps_version (self)) ? + QMI_DMS_FOXCONN_FIRMWARE_VERSION_TYPE_FIRMWARE_MCFG_APPS: + QMI_DMS_FOXCONN_FIRMWARE_VERSION_TYPE_FIRMWARE_MCFG), + NULL); + qmi_client_dms_foxconn_get_firmware_version (QMI_CLIENT_DMS (dms_client), + input, + 10, + NULL, + (GAsyncReadyCallback)dms_foxconn_get_firmware_version_ready, + task); + return; + } + + g_assert_not_reached (); +} + +static void +firmware_load_update_settings (MMIfaceModemFirmware *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMPortMbim *mbim; + + task = g_task_new (self, NULL, callback, user_data); + + mbim = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self)); + mm_port_mbim_allocate_qmi_client (mbim, + QMI_SERVICE_FOX, + NULL, + (GAsyncReadyCallback)mbim_port_allocate_qmi_client_ready, + task); +} + +#endif + +/*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +static MMModemLocationSource +location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +custom_location_load_capabilities (GTask *task, + MMModemLocationSource sources) +{ + MMBroadbandModemMbimFoxconn *self; + + self = g_task_get_source_object (task); + + /* If we have a GPS port and an AT port, enable unmanaged GPS support */ + if (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)) && + mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) { + self->priv->unmanaged_gps_support = FEATURE_SUPPORTED; + sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED; + } + + /* So we're done, complete */ + g_task_return_int (task, sources); + g_object_unref (task); +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + + sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + custom_location_load_capabilities (task, sources); +} + +static void +location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's setup, if any. If MM is built without QMI support, + * the MBIM modem won't have any location capabilities. */ + if (iface_modem_location_parent && + iface_modem_location_parent->load_capabilities && + iface_modem_location_parent->load_capabilities_finish) { + iface_modem_location_parent->load_capabilities (self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + task); + return; + } + + custom_location_load_capabilities (task, MM_MODEM_LOCATION_SOURCE_NONE); +} + +/*****************************************************************************/ +/* Disable location gathering (Location interface) */ + +static gboolean +disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_disable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_location_parent->disable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_disable_location_gathering (GTask *task) +{ + MMIfaceModemLocation *self; + MMModemLocationSource source; + + self = MM_IFACE_MODEM_LOCATION (g_task_get_source_object (task)); + source = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + if (iface_modem_location_parent && + iface_modem_location_parent->disable_location_gathering && + iface_modem_location_parent->disable_location_gathering_finish) { + iface_modem_location_parent->disable_location_gathering ( + self, + source, + (GAsyncReadyCallback)parent_disable_location_gathering_ready, + task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +unmanaged_gps_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + parent_disable_location_gathering (task); +} + +static void +disable_location_gathering (MMIfaceModemLocation *_self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemMbimFoxconn *self = MM_BROADBAND_MODEM_MBIM_FOXCONN (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); + + /* We only support Unmanaged GPS at this level */ + if ((self->priv->unmanaged_gps_support != FEATURE_SUPPORTED) || + (source != MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + parent_disable_location_gathering (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (_self), + "^NV=30007,01,\"00\"", + 3, + FALSE, + (GAsyncReadyCallback)unmanaged_gps_disabled_ready, + task); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +static gboolean +enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +unmanaged_gps_enabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +custom_enable_location_gathering (GTask *task) +{ + MMBroadbandModemMbimFoxconn *self; + MMModemLocationSource source; + + self = g_task_get_source_object (task); + source = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + /* We only support Unmanaged GPS at this level */ + if ((self->priv->unmanaged_gps_support != FEATURE_SUPPORTED) || + (source != MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^NV=30007,01,\"01\"", + 3, + FALSE, + (GAsyncReadyCallback)unmanaged_gps_enabled_ready, + task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_location_parent->enable_location_gathering_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + custom_enable_location_gathering (task); +} + +static void +enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); + + /* Chain up parent's gathering enable */ + if (iface_modem_location_parent && + iface_modem_location_parent->enable_location_gathering && + iface_modem_location_parent->enable_location_gathering_finish) { + iface_modem_location_parent->enable_location_gathering ( + self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); + return; + } + + custom_enable_location_gathering (task); +} + +/*****************************************************************************/ + +MMBroadbandModemMbimFoxconn * +mm_broadband_modem_mbim_foxconn_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + const gchar *carrier_config_mapping = NULL; + + /* T77W968 (DW5821e/DW5829e is also T77W968) modules use t77w968 carrier mapping table. */ + if ((vendor_id == 0x0489 && (product_id == 0xe0b4 || product_id == 0xe0b5)) || + (vendor_id == 0x413c && (product_id == 0x81d7 || product_id == 0x81e0 || product_id == 0x81e4 || product_id == 0x81e6))) + carrier_config_mapping = PKGDATADIR "/mm-foxconn-t77w968-carrier-mapping.conf"; + + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* MBIM bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE, + MM_IFACE_MODEM_LOCATION_ALLOW_GPS_UNMANAGED_ALWAYS, TRUE, + MM_IFACE_MODEM_CARRIER_CONFIG_MAPPING, carrier_config_mapping, + NULL); +} + +static void +mm_broadband_modem_mbim_foxconn_init (MMBroadbandModemMbimFoxconn *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN, MMBroadbandModemMbimFoxconnPrivate); + self->priv->unmanaged_gps_support = FEATURE_SUPPORT_UNKNOWN; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = location_load_capabilities; + iface->load_capabilities_finish = location_load_capabilities_finish; + iface->enable_location_gathering = enable_location_gathering; + iface->enable_location_gathering_finish = enable_location_gathering_finish; + iface->disable_location_gathering = disable_location_gathering; + iface->disable_location_gathering_finish = disable_location_gathering_finish; +} + +#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED + +static void +iface_modem_firmware_init (MMIfaceModemFirmware *iface) +{ + iface->load_update_settings = firmware_load_update_settings; + iface->load_update_settings_finish = firmware_load_update_settings_finish; +} + +#endif + +static void +mm_broadband_modem_mbim_foxconn_class_init (MMBroadbandModemMbimFoxconnClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemMbimFoxconnPrivate)); +} diff --git a/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.h b/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.h new file mode 100644 index 00000000..374599e4 --- /dev/null +++ b/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.h @@ -0,0 +1,49 @@ +/* -*- 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) 2018-2019 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_MBIM_FOXCONN_H +#define MM_BROADBAND_MODEM_MBIM_FOXCONN_H + +#include "mm-broadband-modem-mbim.h" + +#define MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN (mm_broadband_modem_mbim_foxconn_get_type ()) +#define MM_BROADBAND_MODEM_MBIM_FOXCONN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN, MMBroadbandModemMbimFoxconn)) +#define MM_BROADBAND_MODEM_MBIM_FOXCONN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN, MMBroadbandModemMbimFoxconnClass)) +#define MM_IS_BROADBAND_MODEM_MBIM_FOXCONN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN)) +#define MM_IS_BROADBAND_MODEM_MBIM_FOXCONN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN)) +#define MM_BROADBAND_MODEM_MBIM_FOXCONN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN, MMBroadbandModemMbimFoxconnClass)) + +typedef struct _MMBroadbandModemMbimFoxconn MMBroadbandModemMbimFoxconn; +typedef struct _MMBroadbandModemMbimFoxconnClass MMBroadbandModemMbimFoxconnClass; +typedef struct _MMBroadbandModemMbimFoxconnPrivate MMBroadbandModemMbimFoxconnPrivate; + +struct _MMBroadbandModemMbimFoxconn { + MMBroadbandModemMbim parent; + MMBroadbandModemMbimFoxconnPrivate *priv; +}; + +struct _MMBroadbandModemMbimFoxconnClass{ + MMBroadbandModemMbimClass parent; +}; + +GType mm_broadband_modem_mbim_foxconn_get_type (void); + +MMBroadbandModemMbimFoxconn *mm_broadband_modem_mbim_foxconn_new (const gchar *device, + const gchar **driver, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_MBIM_FOXCONN_H */ diff --git a/src/plugins/foxconn/mm-foxconn-t77w968-carrier-mapping.conf b/src/plugins/foxconn/mm-foxconn-t77w968-carrier-mapping.conf new file mode 100644 index 00000000..79b12c1e --- /dev/null +++ b/src/plugins/foxconn/mm-foxconn-t77w968-carrier-mapping.conf @@ -0,0 +1,319 @@ + +# +# T77W968 carrier mapping table +# +# This table maps the MCCMNC of the SIM card with the corresponding +# configuration description as reported by the QMI PDC service in +# this module. +# + +[foxconn t77w968] + +# AT&T +302220=ATT +302221=ATT +31030=ATT +31070=ATT +31090=ATT +310150=ATT +310170=ATT +310280=ATT +310380=ATT +310410=ATT +310560=ATT +310650=ATT +310680=ATT +310980=ATT +311180=ATT +90118=ATT + +# FirstNet +312670=A2 +313100=A2 +313110=A2 +313120=A2 +313130=A2 +313140=A2 + +# Verizon +310590=Verizon +310890=Verizon +311270=Verizon +311480=Verizon +312770=Verizon + +# Vodafone +20205=Vodafone +20404=Vodafone +20601=Vodafone +20810=Vodafone +21401=Vodafone +21670=Vodafone +21910=Vodafone +22005=Vodafone +22210=Vodafone +22601=Vodafone +23003=Vodafone +23201=Vodafone +23415=Vodafone +23801=Vodafone +24405=Vodafone +24602=Vodafone +24705=Vodafone +24802=Vodafone +25001=Vodafone +26202=Vodafone +26209=Vodafone +26801=Vodafone +27077=Vodafone +27201=Vodafone +27402=Vodafone +27602=Vodafone +27801=Vodafone +28001=Vodafone +28401=Vodafone +28602=Vodafone +28802=Vodafone +29340=Vodafone +29403=Vodafone +40004=Vodafone +40401=Vodafone +40405=Vodafone +40411=Vodafone +40413=Vodafone +40415=Vodafone +40420=Vodafone +40427=Vodafone +40430=Vodafone +40443=Vodafone +40446=Vodafone +40460=Vodafone +40484=Vodafone +40486=Vodafone +40488=Vodafone +40566=Vodafone +40567=Vodafone +405750=Vodafone +405751=Vodafone +405752=Vodafone +405753=Vodafone +405754=Vodafone +405755=Vodafone +405756=Vodafone +41302=Vodafone +42403=Vodafone +42602=Vodafone +42702=Vodafone +46601=Vodafone +46603=Vodafone +50213=Vodafone +50219=Vodafone +50503=Vodafone +52503=Vodafone +52505=Vodafone +53001=Vodafone +54201=Vodafone +60202=Vodafone +62002=Vodafone +63001=Vodafone +63902=Vodafone +64004=Vodafone +64304=Vodafone +64710=Vodafone +65101=Vodafone +65501=Vodafone +73001=Vodafone +90128=Vodafone + +# Orange +20610=Orange +20801=Orange +20802=Orange +21403=Orange +21409=Orange +22610=Orange +23101=Orange +23105=Orange +23430=Orange +23433=Orange +23434=Orange +25901=Orange +26003=Orange +26005=Orange +27099=Orange +28310=Orange + +# Telefonica Movistar +21405=Telefonica +21407=Telefonica + +# Swisscom +22801=Swisscom +29501=Swisscom + +# Telstra +50501=Telstra +50506=Telstra +50571=Telstra +50572=Telstra + +# Sprint +310120=Sprint + +# Optus +50202=Optus + +# NTT DoCoMo +44002=Docomo +44003=Docomo +44009=Docomo +44010=Docomo +44011=Docomo +44012=Docomo +44013=Docomo +44014=Docomo +44015=Docomo +44016=Docomo +44017=Docomo +44018=Docomo +44019=Docomo +44022=Docomo +44023=Docomo +44024=Docomo +44025=Docomo +44026=Docomo +44027=Docomo +44028=Docomo +44029=Docomo +44030=Docomo +44031=Docomo +44032=Docomo +44033=Docomo +44034=Docomo +44035=Docomo +44036=Docomo +44037=Docomo +44038=Docomo +44039=Docomo +44049=Docomo +44058=Docomo +44060=Docomo +44061=Docomo +44062=Docomo +44063=Docomo +44064=Docomo +44065=Docomo +44066=Docomo +44067=Docomo +44068=Docomo +44069=Docomo +44087=Docomo +44099=Docomo +44140=Docomo +44141=Docomo +44142=Docomo +44143=Docomo +44144=Docomo +44145=Docomo +44190=Docomo +44101=Docomo +44192=Docomo +44193=Docomo +44194=Docomo +44198=Docomo +44199=Docomo + +# KDDI +44007=KDDI +44008=KDDI +44050=KDDI +44051=KDDI +44052=KDDI +44053=KDDI +44054=KDDI +44055=KDDI +44056=KDDI +44070=KDDI +44071=KDDI +44072=KDDI +44073=KDDI +44074=KDDI +44075=KDDI +44076=KDDI +44077=KDDI +44078=KDDI +44079=KDDI +44080=KDDI +44081=KDDI +44082=KDDI +44083=KDDI +44084=KDDI +44085=KDDI +44086=KDDI +44088=KDDI +44089=KDDI +44150=KDDI +44151=KDDI +44170=KDDI + +# SoftBank +44000=SBM +44004=SBM +44006=SBM +44020=SBM +44021=SBM +44040=SBM +44041=SBM +44042=SBM +44043=SBM +44044=SBM +44045=SBM +44046=SBM +44047=SBM +44048=SBM +44090=SBM +44092=SBM +44093=SBM +44094=SBM +44095=SBM +44096=SBM +44097=SBM +44098=SBM +44101=SBM +44161=SBM +44162=SBM +44163=SBM +44164=SBM +44165=SBM + +# EE UK +23430=EE +23431=EE +23432=EE +23433=EE +23434=EE +23476=EE +23501=EE +23502=EE +23577=EE + +# Deutsche Telekom +20201=DT +20416=DT +20420=DT +21630=DT +21901=DT +22603=DT +22606=DT +23001=DT +23102=DT +23203=DT +23207=DT +26002=DT +26201=DT +27601=DT +29401=DT +29702=DT + +# Others +generic=GCF diff --git a/src/plugins/foxconn/mm-plugin-foxconn.c b/src/plugins/foxconn/mm-plugin-foxconn.c new file mode 100644 index 00000000..d248fb05 --- /dev/null +++ b/src/plugins/foxconn/mm-plugin-foxconn.c @@ -0,0 +1,121 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-foxconn.h" +#include "mm-log-object.h" +#include "mm-broadband-modem.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi.h" +#endif + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim-foxconn.h" +#endif + +G_DEFINE_TYPE (MMPluginFoxconn, mm_plugin_foxconn, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Foxconn-branded modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered Foxconn-branded modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_foxconn_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + mm_obj_dbg (self, "Foxconn-branded generic modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", "wwan", NULL }; + static const guint16 vendor_ids[] = { + 0x0489, /* usb vid */ + 0x105b, /* pci vid */ + 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_FOXCONN, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + NULL)); +} + +static void +mm_plugin_foxconn_init (MMPluginFoxconn *self) +{ +} + +static void +mm_plugin_foxconn_class_init (MMPluginFoxconnClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/foxconn/mm-plugin-foxconn.h b/src/plugins/foxconn/mm-plugin-foxconn.h new file mode 100644 index 00000000..4a22ceeb --- /dev/null +++ b/src/plugins/foxconn/mm-plugin-foxconn.h @@ -0,0 +1,46 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_FOXCONN_H +#define MM_PLUGIN_FOXCONN_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_FOXCONN (mm_plugin_foxconn_get_type ()) +#define MM_PLUGIN_FOXCONN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_FOXCONN, MMPluginFoxconn)) +#define MM_PLUGIN_FOXCONN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_FOXCONN, MMPluginFoxconnClass)) +#define MM_IS_PLUGIN_FOXCONN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_FOXCONN)) +#define MM_IS_PLUGIN_FOXCONN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_FOXCONN)) +#define MM_PLUGIN_FOXCONN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_FOXCONN, MMPluginFoxconnClass)) + +typedef struct { + MMPlugin parent; +} MMPluginFoxconn; + +typedef struct { + MMPluginClass parent; +} MMPluginFoxconnClass; + +GType mm_plugin_foxconn_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_FOXCONN_H */ diff --git a/src/plugins/foxconn/mm-shared.c b/src/plugins/foxconn/mm-shared.c new file mode 100644 index 00000000..3b017574 --- /dev/null +++ b/src/plugins/foxconn/mm-shared.c @@ -0,0 +1,20 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-shared.h" + +MM_SHARED_DEFINE_MAJOR_VERSION +MM_SHARED_DEFINE_MINOR_VERSION +MM_SHARED_DEFINE_NAME(Foxconn) diff --git a/src/plugins/generic/mm-plugin-generic.c b/src/plugins/generic/mm-plugin-generic.c new file mode 100644 index 00000000..6dd37d59 --- /dev/null +++ b/src/plugins/generic/mm-plugin-generic.c @@ -0,0 +1,120 @@ +/* -*- 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) 2008 - 2010 Dan Williams <dcbw@redhat.com> + */ + +#include <string.h> +#include <termios.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <getopt.h> +#include <time.h> + +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-generic.h" +#include "mm-broadband-modem.h" +#include "mm-serial-parsers.h" +#include "mm-log-object.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi.h" +#endif + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#endif + +G_DEFINE_TYPE (MMPluginGeneric, mm_plugin_generic, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered generic modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered generic modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", "wwan", NULL }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_GENERIC, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_IS_GENERIC, TRUE, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_REQUIRED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + NULL)); +} + +static void +mm_plugin_generic_init (MMPluginGeneric *self) +{ +} + +static void +mm_plugin_generic_class_init (MMPluginGenericClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/generic/mm-plugin-generic.h b/src/plugins/generic/mm-plugin-generic.h new file mode 100644 index 00000000..12f9dd9d --- /dev/null +++ b/src/plugins/generic/mm-plugin-generic.h @@ -0,0 +1,40 @@ +/* -*- 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) 2009 Red Hat, Inc. + */ + +#ifndef MM_PLUGIN_GENERIC_H +#define MM_PLUGIN_GENERIC_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_GENERIC (mm_plugin_generic_get_type ()) +#define MM_PLUGIN_GENERIC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_GENERIC, MMPluginGeneric)) +#define MM_PLUGIN_GENERIC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_GENERIC, MMPluginGenericClass)) +#define MM_IS_PLUGIN_GENERIC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_GENERIC)) +#define MM_IS_PLUGIN_GENERIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_GENERIC)) +#define MM_PLUGIN_GENERIC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_GENERIC, MMPluginGenericClass)) + +typedef struct { + MMPlugin parent; +} MMPluginGeneric; + +typedef struct { + MMPluginClass parent; +} MMPluginGenericClass; + +GType mm_plugin_generic_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_GENERIC_H */ diff --git a/src/plugins/generic/tests/test-service-generic.c b/src/plugins/generic/tests/test-service-generic.c new file mode 100644 index 00000000..d7bc4e01 --- /dev/null +++ b/src/plugins/generic/tests/test-service-generic.c @@ -0,0 +1,90 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <sys/types.h> +#include <unistd.h> + +#include <glib.h> +#include <glib-object.h> + +#include <libmm-glib.h> + +#include "test-port-context.h" +#include "test-fixture.h" + +/*****************************************************************************/ + +static void +test_enable_disable (TestFixture *fixture) +{ + GError *error = NULL; + MMObject *obj; + MMModem *modem; + TestPortContext *port0; + gchar *ports [] = { NULL, NULL }; + + /* Create port name, and add process ID so that multiple runs of this test + * in the same system don't clash with each other */ + ports[0] = g_strdup_printf ("abstract:port0:%ld", (glong) getpid ()); + g_debug ("test service generic: using abstract port at '%s'", ports[0]); + + /* Setup new port context */ + port0 = test_port_context_new (ports[0]); + test_port_context_load_commands (port0, COMMON_GSM_PORT_CONF); + test_port_context_start (port0); + + /* Ensure no modem is modem exported */ + test_fixture_no_modem (fixture); + + /* Set the test profile */ + test_fixture_set_profile (fixture, + "test-enable-disable", + "generic", + (const gchar *const *)ports); + + /* Wait and get the modem object */ + obj = test_fixture_get_modem (fixture); + + /* Get Modem interface, and enable */ + modem = mm_object_get_modem (obj); + g_assert (modem != NULL); + mm_modem_enable_sync (modem, NULL, &error); + g_assert_no_error (error); + + /* And disable */ + mm_modem_disable_sync (modem, NULL, &error); + g_assert_no_error (error); + + g_object_unref (modem); + g_object_unref (obj); + + /* Stop port context */ + test_port_context_stop (port0); + test_port_context_free (port0); + + g_free (ports[0]); +} + +/*****************************************************************************/ + +int main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + TEST_ADD ("/MM/Service/Generic/enable-disable", test_enable_disable); + + return g_test_run (); +} diff --git a/src/plugins/gosuncn/77-mm-gosuncn-port-types.rules b/src/plugins/gosuncn/77-mm-gosuncn-port-types.rules new file mode 100644 index 00000000..122c6666 --- /dev/null +++ b/src/plugins/gosuncn/77-mm-gosuncn-port-types.rules @@ -0,0 +1,17 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_gosuncn_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="305a", GOTO="mm_gosuncn_port_types" +GOTO="mm_gosuncn_port_types_end" + +LABEL="mm_gosuncn_port_types" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Gosuncn GM800 +# Interfaces #3 and #4 are MBIM +ATTRS{idVendor}=="305a", ATTRS{idProduct}=="1405", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="305a", ATTRS{idProduct}=="1405", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="305a", ATTRS{idProduct}=="1405", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="305a", ATTRS{idProduct}=="1405", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_IGNORE}="1" + +LABEL="mm_gosuncn_port_types_end" diff --git a/src/plugins/gosuncn/mm-plugin-gosuncn.c b/src/plugins/gosuncn/mm-plugin-gosuncn.c new file mode 100644 index 00000000..010e93eb --- /dev/null +++ b/src/plugins/gosuncn/mm-plugin-gosuncn.c @@ -0,0 +1,114 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <stdlib.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-gosuncn.h" +#include "mm-broadband-modem.h" + +#if defined WITH_QMI +# include "mm-broadband-modem-qmi.h" +#endif + +#if defined WITH_MBIM +# include "mm-broadband-modem-mbim.h" +#endif + +G_DEFINE_TYPE (MMPluginGosuncn, mm_plugin_gosuncn, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Gosuncn modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered Gosuncn modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + /* Fallback to default modem in the worst case */ + return MM_BASE_MODEM (mm_broadband_modem_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x305a, 0 }; + static const gchar *drivers[] = { "qmi_wwan", "cdc_mbim", NULL }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_GOSUNCN, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_DRIVERS, drivers, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + NULL)); +} + +static void +mm_plugin_gosuncn_init (MMPluginGosuncn *self) +{ +} + +static void +mm_plugin_gosuncn_class_init (MMPluginGosuncnClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/gosuncn/mm-plugin-gosuncn.h b/src/plugins/gosuncn/mm-plugin-gosuncn.h new file mode 100644 index 00000000..a50e3089 --- /dev/null +++ b/src/plugins/gosuncn/mm-plugin-gosuncn.h @@ -0,0 +1,40 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_GOSUNCN_H +#define MM_PLUGIN_GOSUNCN_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_GOSUNCN (mm_plugin_gosuncn_get_type ()) +#define MM_PLUGIN_GOSUNCN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_GOSUNCN, MMPluginGosuncn)) +#define MM_PLUGIN_GOSUNCN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_GOSUNCN, MMPluginGosuncnClass)) +#define MM_IS_PLUGIN_GOSUNCN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_GOSUNCN)) +#define MM_IS_PLUGIN_GOSUNCN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_GOSUNCN)) +#define MM_PLUGIN_GOSUNCN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_GOSUNCN, MMPluginGosuncnClass)) + +typedef struct { + MMPlugin parent; +} MMPluginGosuncn; + +typedef struct { + MMPluginClass parent; +} MMPluginGosuncnClass; + +GType mm_plugin_gosuncn_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_GOSUNCN_H */ diff --git a/src/plugins/haier/77-mm-haier-port-types.rules b/src/plugins/haier/77-mm-haier-port-types.rules new file mode 100644 index 00000000..0d969fca --- /dev/null +++ b/src/plugins/haier/77-mm-haier-port-types.rules @@ -0,0 +1,13 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_haier_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="201e", GOTO="mm_haier_port_types" +GOTO="mm_haier_port_types_end" + +LABEL="mm_haier_port_types" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Haier CE81B +ATTRS{idVendor}=="201e", ATTRS{idProduct}=="10f8", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +LABEL="mm_haier_port_types_end" diff --git a/src/plugins/haier/mm-plugin-haier.c b/src/plugins/haier/mm-plugin-haier.c new file mode 100644 index 00000000..a0951c27 --- /dev/null +++ b/src/plugins/haier/mm-plugin-haier.c @@ -0,0 +1,76 @@ +/* -*- 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) 2014 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem.h" +#include "mm-plugin-haier.h" + +G_DEFINE_TYPE (MMPluginHaier, mm_plugin_haier, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + static const guint16 vendor_ids[] = { 0x201e, 0 }; + + return MM_PLUGIN (g_object_new (MM_TYPE_PLUGIN_HAIER, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + NULL)); +} + +static void +mm_plugin_haier_init (MMPluginHaier *self) +{ +} + +static void +mm_plugin_haier_class_init (MMPluginHaierClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/haier/mm-plugin-haier.h b/src/plugins/haier/mm-plugin-haier.h new file mode 100644 index 00000000..e1fdfbb1 --- /dev/null +++ b/src/plugins/haier/mm-plugin-haier.h @@ -0,0 +1,40 @@ +/* -*- 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) 2014 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_HAIER_H +#define MM_PLUGIN_HAIER_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_HAIER (mm_plugin_haier_get_type ()) +#define MM_PLUGIN_HAIER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_HAIER, MMPluginHaier)) +#define MM_PLUGIN_HAIER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_HAIER, MMPluginHaierClass)) +#define MM_IS_PLUGIN_HAIER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_HAIER)) +#define MM_IS_PLUGIN_HAIER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_HAIER)) +#define MM_PLUGIN_HAIER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_HAIER, MMPluginHaierClass)) + +typedef struct { + MMPlugin parent; +} MMPluginHaier; + +typedef struct { + MMPluginClass parent; +} MMPluginHaierClass; + +GType mm_plugin_haier_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_HAIER_H */ diff --git a/src/plugins/huawei/77-mm-huawei-net-port-types.rules b/src/plugins/huawei/77-mm-huawei-net-port-types.rules new file mode 100644 index 00000000..fed7da06 --- /dev/null +++ b/src/plugins/huawei/77-mm-huawei-net-port-types.rules @@ -0,0 +1,37 @@ +# do not edit this file, it will be overwritten on update +ACTION!="add|change|move|bind", GOTO="mm_huawei_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="12d1", GOTO="mm_huawei_port_types" +GOTO="mm_huawei_port_types_end" + +LABEL="mm_huawei_port_types" + +# MU609 does not support getportmode (crashes modem with default firmware) +ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1573", ENV{ID_MM_HUAWEI_DISABLE_GETPORTMODE}="1" + +# Mark the modem and at port flags for ModemManager +SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="01", ATTRS{bInterfaceProtocol}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1" +SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="01", ATTRS{bInterfaceProtocol}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="02", ATTRS{bInterfaceProtocol}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1" +SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="02", ATTRS{bInterfaceProtocol}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +# GPS NMEA port on MU609 +SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="01", ATTRS{bInterfaceProtocol}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +# GPS NMEA port on MU909 +SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="01", ATTRS{bInterfaceProtocol}=="14", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +# GPS NMEA port on MU906e +SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="06", ATTRS{bInterfaceProtocol}=="14", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" + +# Only the standard ECM or NCM port can support dial-up with AT NDISDUP through AT port +SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="02", ATTRS{bInterfaceSubClass}=="06",ATTRS{bInterfaceProtocol}=="00", ENV{ID_MM_HUAWEI_NDISDUP_SUPPORTED}="1" +SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="02", ATTRS{bInterfaceSubClass}=="0d",ATTRS{bInterfaceProtocol}=="00", ENV{ID_MM_HUAWEI_NDISDUP_SUPPORTED}="1" + +# Airtel branded E3372h-607, using huawei-cdc-ncm driver but with unresponsive cdc-wdm port +ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1506", ENV{ID_MM_HUAWEI_NDISDUP_SUPPORTED}="1" + +# R215, Disable CPOL based features +ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1588", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1" + +# E226, Disable CPOL based features +ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1003", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1" + +LABEL="mm_huawei_port_types_end" diff --git a/src/plugins/huawei/mm-broadband-bearer-huawei.c b/src/plugins/huawei/mm-broadband-bearer-huawei.c new file mode 100644 index 00000000..f166efa5 --- /dev/null +++ b/src/plugins/huawei/mm-broadband-bearer-huawei.c @@ -0,0 +1,879 @@ +/* -*- 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) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * Copyright (C) 2012 Huawei Technologies Co., Ltd + * + * Author: Franko fang <huanahu@huawei.com> + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <arpa/inet.h> +#include <ModemManager.h> +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-huawei.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-huawei.h" +#include "mm-daemon-enums-types.h" + +G_DEFINE_TYPE (MMBroadbandBearerHuawei, mm_broadband_bearer_huawei, MM_TYPE_BROADBAND_BEARER) + +struct _MMBroadbandBearerHuaweiPrivate { + gpointer connect_pending; + gpointer disconnect_pending; +}; + +/*****************************************************************************/ + +static MMPortSerialAt * +get_dial_port (MMBroadbandModemHuawei *modem, + MMPort *data, + MMPortSerialAt *primary) +{ + MMPortSerialAt *dial_port; + + /* See if we have a cdc-wdm AT port for the interface */ + dial_port = (mm_broadband_modem_huawei_peek_port_at_for_data ( + MM_BROADBAND_MODEM_HUAWEI (modem), data)); + if (dial_port) + return g_object_ref (dial_port); + + /* Otherwise, fallback to using the primary port for dialing */ + return g_object_ref (primary); +} + +/*****************************************************************************/ +/* Connect 3GPP */ + +typedef enum { + CONNECT_3GPP_CONTEXT_STEP_FIRST = 0, + CONNECT_3GPP_CONTEXT_STEP_NDISDUP, + CONNECT_3GPP_CONTEXT_STEP_NDISSTATQRY, + CONNECT_3GPP_CONTEXT_STEP_IP_CONFIG, + CONNECT_3GPP_CONTEXT_STEP_LAST +} Connect3gppContextStep; + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + MMPort *data; + Connect3gppContextStep step; + guint check_count; + guint failed_ndisstatqry_count; + MMBearerIpConfig *ipv4_config; +} Connect3gppContext; + +static void +connect_3gpp_context_free (Connect3gppContext *ctx) +{ + g_object_unref (ctx->modem); + + g_clear_object (&ctx->ipv4_config); + g_clear_object (&ctx->data); + g_clear_object (&ctx->primary); + + g_slice_free (Connect3gppContext, ctx); +} + +static MMBearerConnectResult * +connect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void connect_3gpp_context_step (GTask *task); + +static void +connect_dhcp_check_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerHuawei *self) +{ + GTask *task; + Connect3gppContext *ctx; + const gchar *response; + GError *error = NULL; + + task = self->priv->connect_pending; + g_assert (task != NULL); + + ctx = g_task_get_task_data (task); + + /* Balance refcount */ + g_object_unref (self); + + /* Cache IPv4 details if available, otherwise clients will have to use DHCP */ + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (response) { + guint address = 0; + guint prefix = 0; + guint gateway = 0; + guint dns1 = 0; + guint dns2 = 0; + + if (mm_huawei_parse_dhcp_response (response, + &address, + &prefix, + &gateway, + &dns1, + &dns2, + &error)) { + GInetAddress *addr; + gchar *strarr[3] = { NULL, NULL, NULL }; + guint n = 0; + gchar *str; + + mm_bearer_ip_config_set_method (ctx->ipv4_config, MM_BEARER_IP_METHOD_STATIC); + + addr = g_inet_address_new_from_bytes ((guint8 *)&address, G_SOCKET_FAMILY_IPV4); + str = g_inet_address_to_string (addr); + mm_bearer_ip_config_set_address (ctx->ipv4_config, str); + g_free (str); + g_object_unref (addr); + + /* Netmask */ + mm_bearer_ip_config_set_prefix (ctx->ipv4_config, prefix); + + /* Gateway */ + addr = g_inet_address_new_from_bytes ((guint8 *)&gateway, G_SOCKET_FAMILY_IPV4); + str = g_inet_address_to_string (addr); + mm_bearer_ip_config_set_gateway (ctx->ipv4_config, str); + g_free (str); + g_object_unref (addr); + + /* DNS */ + if (dns1) { + addr = g_inet_address_new_from_bytes ((guint8 *)&dns1, G_SOCKET_FAMILY_IPV4); + strarr[n++] = g_inet_address_to_string (addr); + g_object_unref (addr); + } + if (dns2) { + addr = g_inet_address_new_from_bytes ((guint8 *)&dns2, G_SOCKET_FAMILY_IPV4); + strarr[n++] = g_inet_address_to_string (addr); + g_object_unref (addr); + } + mm_bearer_ip_config_set_dns (ctx->ipv4_config, (const gchar **)strarr); + g_free (strarr[0]); + g_free (strarr[1]); + } else { + mm_obj_dbg (self, "unexpected response to ^DHCP command: %s", error->message); + } + } + + g_clear_error (&error); + ctx->step++; + connect_3gpp_context_step (task); +} + +static gboolean +connect_retry_ndisstatqry_check_cb (MMBroadbandBearerHuawei *self) +{ + GTask *task; + + /* Recover context */ + task = self->priv->connect_pending; + g_assert (task != NULL); + + /* Balance refcount */ + g_object_unref (self); + + /* Retry same step */ + connect_3gpp_context_step (task); + + return G_SOURCE_REMOVE; +} + +static void +connect_ndisstatqry_check_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerHuawei *self) +{ + GTask *task; + Connect3gppContext *ctx; + const gchar *response; + GError *error = NULL; + gboolean ipv4_available = FALSE; + gboolean ipv4_connected = FALSE; + gboolean ipv6_available = FALSE; + gboolean ipv6_connected = FALSE; + + task = self->priv->connect_pending; + g_assert (task != NULL); + + ctx = g_task_get_task_data (task); + + /* Balance refcount */ + g_object_unref (self); + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (!response || + !mm_huawei_parse_ndisstatqry_response (response, + &ipv4_available, + &ipv4_connected, + &ipv6_available, + &ipv6_connected, + &error)) { + ctx->failed_ndisstatqry_count++; + mm_obj_dbg (self, "unexpected response to ^NDISSTATQRY command: %s (%u attempts so far)", + error->message, ctx->failed_ndisstatqry_count); + g_error_free (error); + } + + /* Connected in IPv4? */ + if (ipv4_available && ipv4_connected) { + /* Success! */ + ctx->step++; + connect_3gpp_context_step (task); + return; + } + + /* Setup timeout to retry the same step */ + g_timeout_add_seconds (1, + (GSourceFunc)connect_retry_ndisstatqry_check_cb, + g_object_ref (self)); +} + +static void +connect_ndisdup_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerHuawei *self) +{ + GTask *task; + Connect3gppContext *ctx; + GError *error = NULL; + + task = self->priv->connect_pending; + g_assert (task != NULL); + + ctx = g_task_get_task_data (task); + + /* Balance refcount */ + g_object_unref (self); + + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + /* Clear task */ + self->priv->connect_pending = NULL; + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go to next step */ + ctx->step++; + connect_3gpp_context_step (task); +} + +typedef enum { + MM_BEARER_HUAWEI_AUTH_UNKNOWN = -1, + MM_BEARER_HUAWEI_AUTH_NONE = 0, + MM_BEARER_HUAWEI_AUTH_PAP = 1, + MM_BEARER_HUAWEI_AUTH_CHAP = 2, + MM_BEARER_HUAWEI_AUTH_MSCHAPV2 = 3, +} MMBearerHuaweiAuthPref; + +static gint +huawei_parse_auth_type (MMBearerAllowedAuth mm_auth) +{ + switch (mm_auth) { + case MM_BEARER_ALLOWED_AUTH_NONE: + return MM_BEARER_HUAWEI_AUTH_NONE; + case MM_BEARER_ALLOWED_AUTH_PAP: + return MM_BEARER_HUAWEI_AUTH_PAP; + case MM_BEARER_ALLOWED_AUTH_CHAP: + return MM_BEARER_HUAWEI_AUTH_CHAP; + case MM_BEARER_ALLOWED_AUTH_MSCHAPV2: + return MM_BEARER_HUAWEI_AUTH_MSCHAPV2; + default: + case MM_BEARER_ALLOWED_AUTH_UNKNOWN: + case MM_BEARER_ALLOWED_AUTH_MSCHAP: + case MM_BEARER_ALLOWED_AUTH_EAP: + return MM_BEARER_HUAWEI_AUTH_UNKNOWN; + } +} + +static void +connect_3gpp_context_step (GTask *task) +{ + MMBroadbandBearerHuawei *self; + Connect3gppContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* Check for cancellation */ + if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) { + /* Clear task */ + self->priv->connect_pending = NULL; + + /* If we already sent the connetion command, send the disconnection one */ + if (ctx->step > CONNECT_3GPP_CONTEXT_STEP_NDISDUP) + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "^NDISDUP=1,0", + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, + FALSE, + FALSE, + NULL, + NULL, /* Do not care the AT response */ + NULL); + + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, + "Huawei connection operation has been cancelled"); + g_object_unref (task); + return; + } + + switch (ctx->step) { + case CONNECT_3GPP_CONTEXT_STEP_FIRST: { + MMBearerIpFamily ip_family; + + ip_family = mm_bearer_properties_get_ip_type (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + mm_3gpp_normalize_ip_family (&ip_family); + if (ip_family != MM_BEARER_IP_FAMILY_IPV4) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Only IPv4 is supported by this modem"); + g_object_unref (task); + return; + } + + /* Store the task */ + self->priv->connect_pending = task; + + ctx->step++; + } /* fall through */ + + case CONNECT_3GPP_CONTEXT_STEP_NDISDUP: { + const gchar *apn; + const gchar *user; + const gchar *passwd; + MMBearerAllowedAuth auth; + gint encoded_auth = MM_BEARER_HUAWEI_AUTH_UNKNOWN; + gchar *command; + + apn = mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + passwd = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + encoded_auth = huawei_parse_auth_type (auth); + + /* Default to no authentication if not specified */ + if (encoded_auth == MM_BEARER_HUAWEI_AUTH_UNKNOWN) + encoded_auth = MM_BEARER_HUAWEI_AUTH_NONE; + + if (!user && !passwd) + command = g_strdup_printf ("AT^NDISDUP=1,1,\"%s\"", + apn == NULL ? "" : apn); + else { + if (encoded_auth == MM_BEARER_HUAWEI_AUTH_NONE) { + encoded_auth = MM_BEARER_HUAWEI_AUTH_CHAP; + mm_obj_dbg (self, "using default (CHAP) authentication method"); + } + command = g_strdup_printf ("AT^NDISDUP=1,1,\"%s\",\"%s\",\"%s\",%d", + apn == NULL ? "" : apn, + user == NULL ? "" : user, + passwd == NULL ? "" : passwd, + encoded_auth); + } + + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)connect_ndisdup_ready, + g_object_ref (self)); + g_free (command); + return; + } + + case CONNECT_3GPP_CONTEXT_STEP_NDISSTATQRY: + /* Wait for dial up timeout, retries for 180 times + * (1s between the retries, so it means 3 minutes). + * If too many retries, failed + */ + if (ctx->check_count > MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT) { + /* Clear context */ + self->priv->connect_pending = NULL; + g_task_return_new_error (task, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, + "Connection attempt timed out"); + g_object_unref (task); + return; + } + + /* Give up if too many unexpected responses to NIDSSTATQRY are encountered. */ + if (ctx->failed_ndisstatqry_count > 10) { + /* Clear context */ + self->priv->connect_pending = NULL; + g_task_return_new_error (task, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED, + "Connection attempt not supported."); + g_object_unref (task); + return; + } + + /* Check if connected */ + ctx->check_count++; + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "^NDISSTATQRY?", + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)connect_ndisstatqry_check_ready, + g_object_ref (self)); + return; + + case CONNECT_3GPP_CONTEXT_STEP_IP_CONFIG: + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "^DHCP?", + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)connect_dhcp_check_ready, + g_object_ref (self)); + return; + + case CONNECT_3GPP_CONTEXT_STEP_LAST: + /* Clear context */ + self->priv->connect_pending = NULL; + + /* Setup result */ + g_task_return_pointer ( + task, + mm_bearer_connect_result_new (ctx->data, ctx->ipv4_config, NULL), + (GDestroyNotify)mm_bearer_connect_result_unref); + + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +connect_3gpp (MMBroadbandBearer *_self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerHuawei *self = MM_BROADBAND_BEARER_HUAWEI (_self); + Connect3gppContext *ctx; + GTask *task; + MMPort *data; + + g_assert (primary != NULL); + + /* We need a net data port */ + data = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET); + if (!data) { + g_task_report_new_error (self, + callback, + user_data, + connect_3gpp, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to launch connection"); + return; + } + + /* Setup connection context */ + ctx = g_slice_new0 (Connect3gppContext); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->data = g_object_ref (data); + ctx->step = CONNECT_3GPP_CONTEXT_STEP_FIRST; + + g_assert (self->priv->connect_pending == NULL); + g_assert (self->priv->disconnect_pending == NULL); + + /* Get correct dial port to use */ + ctx->primary = get_dial_port (MM_BROADBAND_MODEM_HUAWEI (ctx->modem), ctx->data, primary); + + + /* Default to automatic/DHCP addressing */ + ctx->ipv4_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ctx->ipv4_config, MM_BEARER_IP_METHOD_DHCP); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)connect_3gpp_context_free); + g_task_set_check_cancellable (task, FALSE); + + /* Run! */ + connect_3gpp_context_step (task); +} + +/*****************************************************************************/ +/* Disconnect 3GPP */ + +typedef enum { + DISCONNECT_3GPP_CONTEXT_STEP_FIRST = 0, + DISCONNECT_3GPP_CONTEXT_STEP_NDISDUP, + DISCONNECT_3GPP_CONTEXT_STEP_NDISSTATQRY, + DISCONNECT_3GPP_CONTEXT_STEP_LAST +} Disconnect3gppContextStep; + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + Disconnect3gppContextStep step; + guint check_count; + guint failed_ndisstatqry_count; +} Disconnect3gppContext; + +static void +disconnect_3gpp_context_free (Disconnect3gppContext *ctx) +{ + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_slice_free (Disconnect3gppContext, ctx); +} + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void disconnect_3gpp_context_step (GTask *task); + +static gboolean +disconnect_retry_ndisstatqry_check_cb (MMBroadbandBearerHuawei *self) +{ + GTask *task; + + /* Recover context */ + task = self->priv->disconnect_pending; + g_assert (task != NULL); + + /* Balance refcount */ + g_object_unref (self); + + /* Retry same step */ + disconnect_3gpp_context_step (task); + return G_SOURCE_REMOVE; +} + +static void +disconnect_ndisstatqry_check_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerHuawei *self) +{ + GTask *task; + Disconnect3gppContext *ctx; + const gchar *response; + GError *error = NULL; + gboolean ipv4_available = FALSE; + gboolean ipv4_connected = FALSE; + gboolean ipv6_available = FALSE; + gboolean ipv6_connected = FALSE; + + task = self->priv->disconnect_pending; + g_assert (task != NULL); + + ctx = g_task_get_task_data (task); + + /* Balance refcount */ + g_object_unref (self); + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (!response || + !mm_huawei_parse_ndisstatqry_response (response, + &ipv4_available, + &ipv4_connected, + &ipv6_available, + &ipv6_connected, + &error)) { + ctx->failed_ndisstatqry_count++; + mm_obj_dbg (self, "unexpected response to ^NDISSTATQRY command: %s (%u attempts so far)", + error->message, ctx->failed_ndisstatqry_count); + g_error_free (error); + } + + /* Disconnected IPv4? */ + if (ipv4_available && !ipv4_connected) { + /* Success! */ + ctx->step++; + disconnect_3gpp_context_step (task); + return; + } + + /* Setup timeout to retry the same step */ + g_timeout_add_seconds (1, + (GSourceFunc)disconnect_retry_ndisstatqry_check_cb, + g_object_ref (self)); +} + +static void +disconnect_ndisdup_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerHuawei *self) +{ + GTask *task; + Disconnect3gppContext *ctx; + + task = self->priv->disconnect_pending; + g_assert (task != NULL); + + ctx = g_task_get_task_data (task); + + /* Balance refcount */ + g_object_unref (self); + + /* Running NDISDUP=1,0 on an already disconnected bearer/context will + * return ERROR! Ignore errors in the NDISDUP disconnection command, + * because we're anyway going to check the bearer/context status + * afterwards. */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + + /* Go to next step */ + ctx->step++; + disconnect_3gpp_context_step (task); +} + +static void +disconnect_3gpp_context_step (GTask *task) +{ + MMBroadbandBearerHuawei *self; + Disconnect3gppContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case DISCONNECT_3GPP_CONTEXT_STEP_FIRST: + /* Store the task */ + self->priv->disconnect_pending = task; + ctx->step++; + /* fall through */ + + case DISCONNECT_3GPP_CONTEXT_STEP_NDISDUP: + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "^NDISDUP=1,0", + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)disconnect_ndisdup_ready, + g_object_ref (self)); + return; + + case DISCONNECT_3GPP_CONTEXT_STEP_NDISSTATQRY: + /* If too many retries (1s of wait between the retries), failed */ + if (ctx->check_count > MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT) { + /* Clear task */ + self->priv->disconnect_pending = NULL; + g_task_return_new_error (task, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, + "Disconnection attempt timed out"); + g_object_unref (task); + return; + } + + /* Give up if too many unexpected responses to NIDSSTATQRY are encountered. */ + if (ctx->failed_ndisstatqry_count > 10) { + /* Clear task */ + self->priv->disconnect_pending = NULL; + g_task_return_new_error (task, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED, + "Disconnection attempt not supported."); + g_object_unref (task); + return; + } + + /* Check if disconnected */ + ctx->check_count++; + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "^NDISSTATQRY?", + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)disconnect_ndisstatqry_check_ready, + g_object_ref (self)); + return; + + case DISCONNECT_3GPP_CONTEXT_STEP_LAST: + /* Clear task */ + self->priv->disconnect_pending = NULL; + /* Set data port as result */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +disconnect_3gpp (MMBroadbandBearer *_self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerHuawei *self = MM_BROADBAND_BEARER_HUAWEI (_self); + Disconnect3gppContext *ctx; + GTask *task; + + g_assert (primary != NULL); + + ctx = g_slice_new0 (Disconnect3gppContext); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->step = DISCONNECT_3GPP_CONTEXT_STEP_FIRST; + + g_assert (self->priv->connect_pending == NULL); + g_assert (self->priv->disconnect_pending == NULL); + + /* Get correct dial port to use */ + ctx->primary = get_dial_port (MM_BROADBAND_MODEM_HUAWEI (ctx->modem), data, primary); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)disconnect_3gpp_context_free); + + /* Start! */ + disconnect_3gpp_context_step (task); +} + +/*****************************************************************************/ + +static void +report_connection_status (MMBaseBearer *bearer, + MMBearerConnectionStatus status, + const GError *connection_error) +{ + MMBroadbandBearerHuawei *self = MM_BROADBAND_BEARER_HUAWEI (bearer); + + g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED || + status == MM_BEARER_CONNECTION_STATUS_DISCONNECTING || + status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED); + + /* When a pending connection / disconnection attempt is in progress, we use + * ^NDISSTATQRY? to check the connection status and thus temporarily ignore + * ^NDISSTAT unsolicited messages */ + if (self->priv->connect_pending || self->priv->disconnect_pending) + return; + + mm_obj_dbg (self, "received spontaneous ^NDISSTAT (%s)", mm_bearer_connection_status_get_string (status)); + + /* Ignore 'CONNECTED' */ + if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED) + return; + + /* Report disconnected right away */ + MM_BASE_BEARER_CLASS (mm_broadband_bearer_huawei_parent_class)->report_connection_status ( + bearer, + MM_BEARER_CONNECTION_STATUS_DISCONNECTED, + NULL); +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_broadband_bearer_huawei_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *bearer; + GObject *source; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_huawei_new (MMBroadbandModemHuawei *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_HUAWEI, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + NULL); +} + +static void +mm_broadband_bearer_huawei_init (MMBroadbandBearerHuawei *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_BEARER_HUAWEI, + MMBroadbandBearerHuaweiPrivate); +} + +static void +mm_broadband_bearer_huawei_class_init (MMBroadbandBearerHuaweiClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandBearerHuaweiPrivate)); + + base_bearer_class->report_connection_status = report_connection_status; + base_bearer_class->load_connection_status = NULL; + base_bearer_class->load_connection_status_finish = NULL; +#if defined WITH_SUSPEND_RESUME + base_bearer_class->reload_connection_status = NULL; + base_bearer_class->reload_connection_status_finish = NULL; +#endif + + broadband_bearer_class->connect_3gpp = connect_3gpp; + broadband_bearer_class->connect_3gpp_finish = connect_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; +} diff --git a/src/plugins/huawei/mm-broadband-bearer-huawei.h b/src/plugins/huawei/mm-broadband-bearer-huawei.h new file mode 100644 index 00000000..d3f43abc --- /dev/null +++ b/src/plugins/huawei/mm-broadband-bearer-huawei.h @@ -0,0 +1,59 @@ +/* -*- 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) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * Copyright (C) 2012 Huawei Technologies Co., Ltd + * + * Author: Franko Fang <huananhu@huawei.com> + */ + +#ifndef MM_BROADBAND_BEARER_HUAWEI_H +#define MM_BROADBAND_BEARER_HUAWEI_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-huawei.h" + +#define MM_TYPE_BROADBAND_BEARER_HUAWEI (mm_broadband_bearer_huawei_get_type ()) +#define MM_BROADBAND_BEARER_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_HUAWEI, MMBroadbandBearerHuawei)) +#define MM_BROADBAND_BEARER_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_HUAWEI, MMBroadbandBearerHuaweiClass)) +#define MM_IS_BROADBAND_BEARER_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_HUAWEI)) +#define MM_IS_BROADBAND_BEARER_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_HUAWEI)) +#define MM_BROADBAND_BEARER_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_HUAWEI, MMBroadbandBearerHuaweiClass)) + +typedef struct _MMBroadbandBearerHuawei MMBroadbandBearerHuawei; +typedef struct _MMBroadbandBearerHuaweiClass MMBroadbandBearerHuaweiClass; +typedef struct _MMBroadbandBearerHuaweiPrivate MMBroadbandBearerHuaweiPrivate; + +struct _MMBroadbandBearerHuawei { + MMBroadbandBearer parent; + MMBroadbandBearerHuaweiPrivate *priv; +}; + +struct _MMBroadbandBearerHuaweiClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_huawei_get_type (void); + +void mm_broadband_bearer_huawei_new (MMBroadbandModemHuawei *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_huawei_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_BROADBAND_BEARER_HUAWEI_H */ diff --git a/src/plugins/huawei/mm-broadband-modem-huawei.c b/src/plugins/huawei/mm-broadband-modem-huawei.c new file mode 100644 index 00000000..c7c68b9f --- /dev/null +++ b/src/plugins/huawei/mm-broadband-modem-huawei.c @@ -0,0 +1,4732 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2011 - 2012 Google Inc. + * Copyright (C) 2012 Huawei Technologies Co., Ltd + * Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it> + * Copyright (C) 2012 - 2019 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <time.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-huawei.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-3gpp-ussd.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-time.h" +#include "mm-iface-modem-cdma.h" +#include "mm-iface-modem-signal.h" +#include "mm-iface-modem-voice.h" +#include "mm-broadband-modem-huawei.h" +#include "mm-broadband-bearer-huawei.h" +#include "mm-broadband-bearer.h" +#include "mm-bearer-list.h" +#include "mm-sim-huawei.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_cdma_init (MMIfaceModemCdma *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); +static void iface_modem_voice_init (MMIfaceModemVoice *iface); +static void iface_modem_signal_init (MMIfaceModemSignal *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModemLocation *iface_modem_location_parent; +static MMIfaceModemCdma *iface_modem_cdma_parent; +static MMIfaceModemVoice *iface_modem_voice_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemHuawei, mm_broadband_modem_huawei, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_USSD, iface_modem_3gpp_ussd_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)) + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED +} FeatureSupport; + +typedef struct { + MMSignal *cdma; + MMSignal *evdo; + MMSignal *gsm; + MMSignal *umts; + MMSignal *lte; + MMSignal *nr5g; +} DetailedSignal; + +struct _MMBroadbandModemHuaweiPrivate { + /* Regex for signal quality related notifications */ + GRegex *rssi_regex; + GRegex *rssilvl_regex; + GRegex *hrssilvl_regex; + + /* Regex for access-technology related notifications */ + GRegex *mode_regex; + + /* Regex for connection status related notifications */ + GRegex *dsflowrpt_regex; + GRegex *ndisstat_regex; + + /* Regex for voice management notifications */ + GRegex *orig_regex; + GRegex *conf_regex; + GRegex *conn_regex; + GRegex *cend_regex; + GRegex *ddtmf_regex; + + /* Regex to ignore */ + GRegex *boot_regex; + GRegex *connect_regex; + GRegex *csnr_regex; + GRegex *cusatp_regex; + GRegex *cusatend_regex; + GRegex *dsdormant_regex; + GRegex *simst_regex; + GRegex *srvst_regex; + GRegex *stin_regex; + GRegex *hcsq_regex; + GRegex *pdpdeact_regex; + GRegex *ndisend_regex; + GRegex *rfswitch_regex; + GRegex *position_regex; + GRegex *posend_regex; + GRegex *ecclist_regex; + GRegex *ltersrp_regex; + GRegex *cschannelinfo_regex; + GRegex *ccallstate_regex; + GRegex *eons_regex; + GRegex *lwurc_regex; + + FeatureSupport ndisdup_support; + FeatureSupport rfswitch_support; + FeatureSupport sysinfoex_support; + FeatureSupport syscfg_support; + FeatureSupport syscfgex_support; + FeatureSupport prefmode_support; + FeatureSupport time_support; + FeatureSupport nwtime_support; + FeatureSupport cvoice_support; + + MMModemLocationSource enabled_sources; + + GArray *syscfg_supported_modes; + GArray *syscfgex_supported_modes; + GArray *prefmode_supported_modes; + + DetailedSignal detailed_signal; + + /* Voice call audio related properties */ + guint audio_hz; + guint audio_bits; +}; + +/*****************************************************************************/ + +GList * +mm_broadband_modem_huawei_get_at_port_list (MMBroadbandModemHuawei *self) +{ + GList *out = NULL; + MMPortSerialAt *port; + GList *cdc_wdm_at_ports; + + /* Primary */ + port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); + if (port) + out = g_list_append (out, port); + + /* Secondary */ + port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); + if (port) + out = g_list_append (out, port); + + /* Additional cdc-wdm ports used for dialing */ + cdc_wdm_at_ports = mm_base_modem_find_ports (MM_BASE_MODEM (self), + MM_PORT_SUBSYS_USBMISC, + MM_PORT_TYPE_AT); + + return g_list_concat (out, cdc_wdm_at_ports); +} + +/*****************************************************************************/ + +typedef struct { + gboolean extended; + guint srv_status; + guint srv_domain; + guint roam_status; + guint sim_state; + guint sys_mode; + gboolean sys_submode_valid; + guint sys_submode; +} SysinfoResult; + +static gboolean +sysinfo_finish (MMBroadbandModemHuawei *self, + GAsyncResult *res, + gboolean *extended, + guint *srv_status, + guint *srv_domain, + guint *roam_status, + guint *sim_state, + guint *sys_mode, + gboolean *sys_submode_valid, + guint *sys_submode, + GError **error) +{ + SysinfoResult *result; + + result = g_task_propagate_pointer (G_TASK (res), error); + if (!result) + return FALSE; + + if (extended) + *extended = result->extended; + if (srv_status) + *srv_status = result->srv_status; + if (srv_domain) + *srv_domain = result->srv_domain; + if (roam_status) + *roam_status = result->roam_status; + if (sim_state) + *sim_state = result->sim_state; + if (sys_mode) + *sys_mode = result->sys_mode; + if (sys_submode_valid) + *sys_submode_valid = result->sys_submode_valid; + if (sys_submode) + *sys_submode = result->sys_submode; + + g_free (result); + return TRUE; +} + +static void +run_sysinfo_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + SysinfoResult *result; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + mm_obj_dbg (self, "^SYSINFO failed: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + result = g_new0 (SysinfoResult, 1); + result->extended = FALSE; + if (!mm_huawei_parse_sysinfo_response (response, + &result->srv_status, + &result->srv_domain, + &result->roam_status, + &result->sys_mode, + &result->sim_state, + &result->sys_submode_valid, + &result->sys_submode, + &error)) { + mm_obj_dbg (self, "^SYSINFO parsing failed: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + g_free (result); + return; + } + + g_task_return_pointer (task, result, g_free); + g_object_unref (task); +} + +static void +run_sysinfo (MMBroadbandModemHuawei *self, + GTask *task) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SYSINFO", + 3, + FALSE, + (GAsyncReadyCallback)run_sysinfo_ready, + task); +} + +static void +run_sysinfoex_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GError *error = NULL; + const gchar *response; + SysinfoResult *result; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) { + /* First time we try, we fallback to ^SYSINFO */ + if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN) { + self->priv->sysinfoex_support = FEATURE_NOT_SUPPORTED; + mm_obj_dbg (self, "^SYSINFOEX failed: %s, assuming unsupported", error->message); + g_error_free (error); + run_sysinfo (self, task); + return; + } + + /* Otherwise, propagate error */ + mm_obj_dbg (self, "^SYSINFOEX failed: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN) + self->priv->sysinfoex_support = FEATURE_SUPPORTED; + + result = g_new0 (SysinfoResult, 1); + result->extended = TRUE; + if (!mm_huawei_parse_sysinfoex_response (response, + &result->srv_status, + &result->srv_domain, + &result->roam_status, + &result->sim_state, + &result->sys_mode, + &result->sys_submode, + &error)) { + mm_obj_dbg (self, "^SYSINFOEX parsing failed: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + g_free (result); + return; + } + + /* Submode from SYSINFOEX always valid */ + result->sys_submode_valid = TRUE; + g_task_return_pointer (task, result, g_free); + g_object_unref (task); +} + +static void +run_sysinfoex (MMBroadbandModemHuawei *self, + GTask *task) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SYSINFOEX", + 3, + FALSE, + (GAsyncReadyCallback)run_sysinfoex_ready, + task); +} + +static void +sysinfo (MMBroadbandModemHuawei *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN || + self->priv->sysinfoex_support == FEATURE_SUPPORTED) + run_sysinfoex (self, task); + else + run_sysinfo (self, task); +} + +/*****************************************************************************/ +/* Reset (Modem interface) */ + +static gboolean +reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + const gchar *command; + + /* Unlike other Huawei modems that support AT^RESET for resetting the modem, + * Huawei MU736 supports AT^RESET but does not reset the modem upon receiving + * AT^RESET. It does, however, support resetting itself via AT+CFUN=16. + */ + if (g_strcmp0 (mm_iface_modem_get_model (self), "MU736") == 0) + command = "+CFUN=16"; + else + command = "^RESET"; + + mm_base_modem_at_command (MM_BASE_MODEM (self), + command, + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +static MMModemAccessTechnology +huawei_sysinfo_submode_to_act (guint submode) +{ + /* new more detailed system mode/access technology */ + switch (submode) { + case 1: + return MM_MODEM_ACCESS_TECHNOLOGY_GSM; + case 2: + return MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + case 3: + return MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + case 4: + return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + case 5: + return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; + case 6: + return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA; + case 7: + return MM_MODEM_ACCESS_TECHNOLOGY_HSPA; + case 8: /* TD-SCDMA */ + break; + case 9: /* HSPA+ */ + return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; + case 10: + return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; + case 11: + return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA; + case 12: + return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB; + case 13: /* 1xRTT */ + return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; + case 16: /* 3xRTT */ + return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; + case 17: /* HSPA+ (64QAM) */ + return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; + case 18: /* HSPA+ (MIMO) */ + return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; + default: + break; + } + + return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; +} + +static MMModemAccessTechnology +huawei_sysinfo_mode_to_act (guint mode) +{ + /* Older, less detailed system mode/access technology */ + switch (mode) { + case 1: /* AMPS */ + break; + case 2: /* CDMA */ + return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; + case 3: /* GSM/GPRS */ + return MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + case 4: /* HDR */ + return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; + case 5: /* WCDMA */ + return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + case 6: /* GPS */ + break; + case 7: /* GSM/WCDMA */ + return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + case 8: /* CDMA/HDR hybrid */ + return (MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 | MM_MODEM_ACCESS_TECHNOLOGY_1XRTT); + case 15: /* TD-SCDMA */ + break; + default: + break; + } + + return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; +} + +static MMModemAccessTechnology +huawei_sysinfoex_submode_to_act (guint submode) +{ + switch (submode) { + case 1: /* GSM */ + return MM_MODEM_ACCESS_TECHNOLOGY_GSM; + case 2: /* GPRS */ + return MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + case 3: /* EDGE */ + return MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + + case 21: /* IS95A */ + return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; + case 22: /* IS95B */ + return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; + case 23: /* CDMA2000 1x */ + return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; + case 24: /* EVDO rel0 */ + return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; + case 25: /* EVDO relA */ + return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA; + case 26: /* EVDO relB */ + return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB; + case 27: /* Hybrid CDMA2000 1x */ + return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; + case 28: /* Hybrid EVDO rel0 */ + return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; + case 29: /* Hybrid EVDO relA */ + return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA; + case 30: /* Hybrid EVDO relB */ + return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB; + + case 41: /* WCDMA */ + return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + case 42: /* HSDPA */ + return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; + case 43: /* HSUPA */ + return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA; + case 44: /* HSPA */ + return MM_MODEM_ACCESS_TECHNOLOGY_HSPA; + case 45: /* HSPA+ */ + return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; + case 46: /* DC-HSPA+ */ + return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; + + case 61: /* TD-SCDMA */ + break; + + case 81: /* 802.16e (WiMAX) */ + break; + + case 101: /* LTE */ + return MM_MODEM_ACCESS_TECHNOLOGY_LTE; + + default: + break; + } + + return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; +} + +static MMModemAccessTechnology +huawei_sysinfoex_mode_to_act (guint mode) +{ + /* Older, less detailed system mode/access technology */ + switch (mode) { + case 1: /* GSM */ + return MM_MODEM_ACCESS_TECHNOLOGY_GSM; + case 2: /* CDMA */ + return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; + case 3: /* WCDMA */ + return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + case 4: /* TD-SCDMA */ + break; + case 5: /* WIMAX */ + break; + case 6: /* LTE */ + return MM_MODEM_ACCESS_TECHNOLOGY_LTE; + default: + break; + } + + return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; +} + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + gboolean extended = FALSE; + guint srv_status = 0; + gboolean sys_submode_valid = FALSE; + guint sys_submode = 0; + guint sys_mode = 0; + + if (!sysinfo_finish (MM_BROADBAND_MODEM_HUAWEI (self), + res, + &extended, + &srv_status, + NULL, /* srv_domain */ + NULL, /* roam_status */ + NULL, /* sim_state */ + &sys_mode, + &sys_submode_valid, + &sys_submode, + error)) + return FALSE; + + if (srv_status != 0) { + /* Valid service */ + if (sys_submode_valid) + act = (extended ? + huawei_sysinfoex_submode_to_act (sys_submode) : + huawei_sysinfo_submode_to_act (sys_submode)); + + if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN) + act = (extended ? + huawei_sysinfoex_mode_to_act (sys_mode) : + huawei_sysinfo_mode_to_act (sys_mode)); + } + + *access_technologies = act; + *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; + return TRUE; +} + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + sysinfo (MM_BROADBAND_MODEM_HUAWEI (self), callback, user_data); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + MMUnlockRetries *unlock_retries; + const gchar *result; + GError *match_error = NULL; + guint i; + + MMModemLock locks[4] = { + MM_MODEM_LOCK_SIM_PUK, + MM_MODEM_LOCK_SIM_PIN, + MM_MODEM_LOCK_SIM_PUK2, + MM_MODEM_LOCK_SIM_PIN2 + }; + + result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!result) + return NULL; + + r = g_regex_new ("\\^CPIN:\\s*([^,]+),[^,]*,(\\d+),(\\d+),(\\d+),(\\d+)", + G_REGEX_UNGREEDY, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, result, strlen (result), 0, 0, &match_info, &match_error)) { + if (match_error) + g_propagate_error (error, match_error); + else + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Could not parse ^CPIN results: Response didn't match (%s)", + result); + return NULL; + } + + unlock_retries = mm_unlock_retries_new (); + for (i = 0; i <= 3; i++) { + guint num; + + if (!mm_get_uint_from_match_info (match_info, i + 2, &num) || + num > 10) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Could not parse ^CPIN results: " + "Missing or invalid match info for lock '%s'", + mm_modem_lock_get_string (locks[i])); + g_object_unref (unlock_retries); + unlock_retries = NULL; + break; + } + + mm_unlock_retries_set (unlock_retries, locks[i], num); + } + + return unlock_retries; +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^CPIN?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* After SIM unlock (Modem interface) */ + +static gboolean +modem_after_sim_unlock_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +after_sim_unlock_wait_cb (GTask *task) +{ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +modem_after_sim_unlock (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* A 3-second wait is necessary for SIM to become ready, or the firmware may + * fail miserably and reboot itself */ + g_timeout_add_seconds (3, (GSourceFunc)after_sim_unlock_wait_cb, task); +} + +/*****************************************************************************/ +/* Common band/mode handling code */ + +typedef struct { + MMModemBand mm; + guint32 huawei; +} BandTable; + +static BandTable bands[] = { + /* Sort 3G first since it's preferred */ + { MM_MODEM_BAND_UTRAN_1, 0x00400000 }, + { MM_MODEM_BAND_UTRAN_2, 0x00800000 }, + { MM_MODEM_BAND_UTRAN_5, 0x04000000 }, + { MM_MODEM_BAND_UTRAN_8, 0x00020000 }, + /* 2G second */ + { MM_MODEM_BAND_G850, 0x00080000 }, + { MM_MODEM_BAND_DCS, 0x00000080 }, + { MM_MODEM_BAND_EGSM, 0x00000100 }, + { MM_MODEM_BAND_PCS, 0x00200000 } +}; + +static gboolean +bands_array_to_huawei (GArray *bands_array, + guint32 *out_huawei) +{ + guint i; + + /* Treat ANY as a special case: All huawei flags enabled */ + if (bands_array->len == 1 && + g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) { + *out_huawei = 0x3FFFFFFF; + return TRUE; + } + + *out_huawei = 0; + for (i = 0; i < bands_array->len; i++) { + guint j; + + for (j = 0; j < G_N_ELEMENTS (bands); j++) { + if (g_array_index (bands_array, MMModemBand, i) == bands[j].mm) + *out_huawei |= bands[j].huawei; + } + } + + return (*out_huawei > 0 ? TRUE : FALSE); +} + +static gboolean +huawei_to_bands_array (guint32 huawei, + GArray **bands_array, + GError **error) +{ + guint i; + + *bands_array = NULL; + for (i = 0; i < G_N_ELEMENTS (bands); i++) { + if (huawei & bands[i].huawei) { + if (G_UNLIKELY (!*bands_array)) + *bands_array = g_array_new (FALSE, FALSE, sizeof (MMModemBand)); + g_array_append_val (*bands_array, bands[i].mm); + } + } + + if (!*bands_array) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't build bands array from '%u'", + huawei); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_syscfg (const gchar *response, + GArray **bands_array, + GError **error) +{ + gint mode; + gint acquisition_order; + guint32 band; + gint roaming; + gint srv_domain; + + if (!response || + strncmp (response, "^SYSCFG:", 8) != 0 || + !sscanf (response + 8, "%d,%d,%x,%d,%d", &mode, &acquisition_order, &band, &roaming, &srv_domain)) { + /* Dump error to upper layer */ + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unexpected SYSCFG response: '%s'", + response); + return FALSE; + } + + /* Band */ + if (bands_array && + !huawei_to_bands_array (band, bands_array, error)) + return FALSE; + + return TRUE; +} + +/*****************************************************************************/ +/* Load current bands (Modem interface) */ + +static GArray * +load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + const gchar *response; + GArray *bands_array = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return NULL; + + if (!parse_syscfg (response, &bands_array, error)) + return NULL; + + return bands_array; +} + +static void +load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SYSCFG?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Set current bands (Modem interface) */ + +static gboolean +set_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +syscfg_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) + /* Let the error be critical */ + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +set_current_bands (MMIfaceModem *self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *cmd; + guint32 huawei_band = 0x3FFFFFFF; + gchar *bands_string; + + task = g_task_new (self, NULL, callback, user_data); + + bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands_array->data, + bands_array->len); + + if (!bands_array_to_huawei (bands_array, &huawei_band)) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid bands requested: '%s'", + bands_string); + g_object_unref (task); + g_free (bands_string); + return; + } + + cmd = g_strdup_printf ("AT^SYSCFG=16,3,%X,2,4", huawei_band); + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 3, + FALSE, + (GAsyncReadyCallback)syscfg_set_ready, + task); + g_free (cmd); + g_free (bands_string); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +syscfg_test_ready (MMBroadbandModemHuawei *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (response) { + /* There are 2G+3G Huawei modems out there which support mode switching with + * AT^SYSCFG, but fail to provide a valid response for AT^SYSCFG=? (they just + * return an empty string). So handle that case by providing a default response + * string to get parsed. Ugly, ugly, blame Huawei. + */ + if (response[0]) + self->priv->syscfg_supported_modes = mm_huawei_parse_syscfg_test (response, self, &error); + else { + self->priv->syscfg_supported_modes = mm_huawei_parse_syscfg_test (MM_HUAWEI_DEFAULT_SYSCFG_FMT, self, NULL); + g_assert (self->priv->syscfg_supported_modes != NULL); + } + } + + if (self->priv->syscfg_supported_modes) { + MMModemModeCombination mode; + guint i; + GArray *combinations; + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, + FALSE, + sizeof (MMModemModeCombination), + self->priv->syscfg_supported_modes->len); + for (i = 0; i < self->priv->syscfg_supported_modes->len; i++) { + MMHuaweiSyscfgCombination *huawei_mode; + + huawei_mode = &g_array_index (self->priv->syscfg_supported_modes, + MMHuaweiSyscfgCombination, + i); + mode.allowed = huawei_mode->allowed; + mode.preferred = huawei_mode->preferred; + g_array_append_val (combinations, mode); + } + + self->priv->syscfg_support = FEATURE_SUPPORTED; + g_task_return_pointer (task, + combinations, + (GDestroyNotify)g_array_unref); + } else { + mm_obj_dbg (self, "error while checking ^SYSCFG format: %s", error->message); + /* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */ + if (!g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)) + self->priv->syscfg_support = FEATURE_NOT_SUPPORTED; + g_task_return_error (task, error); + } + + g_object_unref (task); +} + +static void +syscfgex_test_ready (MMBroadbandModemHuawei *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (response) + self->priv->syscfgex_supported_modes = mm_huawei_parse_syscfgex_test (response, &error); + + if (self->priv->syscfgex_supported_modes) { + MMModemModeCombination mode; + guint i; + GArray *combinations; + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, + FALSE, + sizeof (MMModemModeCombination), + self->priv->syscfgex_supported_modes->len); + for (i = 0; i < self->priv->syscfgex_supported_modes->len; i++) { + MMHuaweiSyscfgexCombination *huawei_mode; + + huawei_mode = &g_array_index (self->priv->syscfgex_supported_modes, + MMHuaweiSyscfgexCombination, + i); + mode.allowed = huawei_mode->allowed; + mode.preferred = huawei_mode->preferred; + g_array_append_val (combinations, mode); + } + + self->priv->syscfgex_support = FEATURE_SUPPORTED; + + g_task_return_pointer (task, + combinations, + (GDestroyNotify)g_array_unref); + g_object_unref (task); + return; + } + + /* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */ + if (error) { + mm_obj_dbg (self, "error while checking ^SYSCFGEX format: %s", error->message); + if (g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + g_error_free (error); + } + + self->priv->syscfgex_support = FEATURE_NOT_SUPPORTED; + + /* Try with SYSCFG */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SYSCFG=?", + 3, + TRUE, + (GAsyncReadyCallback)syscfg_test_ready, + task); +} + +static void +prefmode_test_ready (MMBroadbandModemHuawei *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (response) + self->priv->prefmode_supported_modes = mm_huawei_parse_prefmode_test (response, self, &error); + + if (self->priv->prefmode_supported_modes) { + MMModemModeCombination mode; + guint i; + GArray *combinations; + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, + FALSE, + sizeof (MMModemModeCombination), + self->priv->prefmode_supported_modes->len); + for (i = 0; i < self->priv->prefmode_supported_modes->len; i++) { + MMHuaweiPrefmodeCombination *huawei_mode; + + huawei_mode = &g_array_index (self->priv->prefmode_supported_modes, + MMHuaweiPrefmodeCombination, + i); + mode.allowed = huawei_mode->allowed; + mode.preferred = huawei_mode->preferred; + g_array_append_val (combinations, mode); + } + + self->priv->prefmode_support = FEATURE_SUPPORTED; + g_task_return_pointer (task, + combinations, + (GDestroyNotify)g_array_unref); + } else { + mm_obj_dbg (self, "error while checking ^PREFMODE format: %s", error->message); + /* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */ + if (!g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)) + self->priv->prefmode_support = FEATURE_NOT_SUPPORTED; + g_task_return_error (task, error); + } + + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + if (mm_iface_modem_is_cdma_only (_self)) { + /* ^PREFMODE only in CDMA-only modems */ + self->priv->syscfg_support = FEATURE_NOT_SUPPORTED; + self->priv->syscfgex_support = FEATURE_NOT_SUPPORTED; + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^PREFMODE=?", + 3, + TRUE, + (GAsyncReadyCallback)prefmode_test_ready, + task); + return; + } + + /* Check SYSCFGEX */ + self->priv->prefmode_support = FEATURE_NOT_SUPPORTED; + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SYSCFGEX=?", + 3, + TRUE, + (GAsyncReadyCallback)syscfgex_test_ready, + task); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + MMModemModeCombination *out; + + out = g_task_propagate_pointer (G_TASK (res), error); + if (!out) + return FALSE; + + *allowed = out->allowed; + *preferred = out->preferred; + + g_free (out); + return TRUE; +} + +static void +prefmode_load_current_modes_ready (MMBroadbandModemHuawei *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + const MMHuaweiPrefmodeCombination *current = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (response) + current = mm_huawei_parse_prefmode_response (response, + self->priv->prefmode_supported_modes, + &error); + + if (error) + g_task_return_error (task, error); + else { + MMModemModeCombination *out; + + out = g_new (MMModemModeCombination, 1); + out->allowed = current->allowed; + out->preferred = current->preferred; + g_task_return_pointer (task, out, g_free); + } + g_object_unref (task); +} + +static void +syscfg_load_current_modes_ready (MMBroadbandModemHuawei *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + const MMHuaweiSyscfgCombination *current = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (response) + current = mm_huawei_parse_syscfg_response (response, + self->priv->syscfg_supported_modes, + &error); + + if (error) + g_task_return_error (task, error); + else { + MMModemModeCombination *out; + + out = g_new (MMModemModeCombination, 1); + out->allowed = current->allowed; + out->preferred = current->preferred; + g_task_return_pointer (task, out, g_free); + } + g_object_unref (task); +} + +static void +syscfgex_load_current_modes_ready (MMBroadbandModemHuawei *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + const MMHuaweiSyscfgexCombination *current = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (response) + current = mm_huawei_parse_syscfgex_response (response, + self->priv->syscfgex_supported_modes, + &error); + if (error) + g_task_return_error (task, error); + else { + MMModemModeCombination *out; + + out = g_new (MMModemModeCombination, 1); + out->allowed = current->allowed; + out->preferred = current->preferred; + g_task_return_pointer (task, out, g_free); + } + g_object_unref (task); +} + +static void +load_current_modes (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + if (self->priv->syscfgex_support == FEATURE_SUPPORTED) { + g_assert (self->priv->syscfgex_supported_modes != NULL); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "^SYSCFGEX?", + 3, + FALSE, + (GAsyncReadyCallback)syscfgex_load_current_modes_ready, + task); + return; + } + + if (self->priv->syscfg_support == FEATURE_SUPPORTED) { + g_assert (self->priv->syscfg_supported_modes != NULL); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "^SYSCFG?", + 3, + FALSE, + (GAsyncReadyCallback)syscfg_load_current_modes_ready, + task); + return; + } + + if (self->priv->prefmode_support == FEATURE_SUPPORTED) { + g_assert (self->priv->prefmode_supported_modes != NULL); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "^PREFMODE?", + 3, + FALSE, + (GAsyncReadyCallback)prefmode_load_current_modes_ready, + task); + return; + } + + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unable to load current modes"); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Set current modes (Modem interface) */ + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +set_current_modes_ready (MMBroadbandModemHuawei *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (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); +} + +static gboolean +prefmode_set_current_modes (MMBroadbandModemHuawei *self, + MMModemMode allowed, + MMModemMode preferred, + GTask *task, + GError **error) +{ + guint i; + MMHuaweiPrefmodeCombination *found = NULL; + gchar *command; + + for (i = 0; i < self->priv->prefmode_supported_modes->len; i++) { + MMHuaweiPrefmodeCombination *single; + + single = &g_array_index (self->priv->prefmode_supported_modes, + MMHuaweiPrefmodeCombination, + i); + if (single->allowed == allowed && single->preferred == preferred) { + found = single; + break; + } + } + + if (!found) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "Requested mode ^PREFMODE combination not found"); + return FALSE; + } + + command = g_strdup_printf ("^PREFMODE=%u", found->prefmode); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)set_current_modes_ready, + task); + g_free (command); + return TRUE; +} + +static gboolean +syscfg_set_current_modes (MMBroadbandModemHuawei *self, + MMModemMode allowed, + MMModemMode preferred, + GTask *task, + GError **error) +{ + guint i; + MMHuaweiSyscfgCombination *found = NULL; + gchar *command; + + for (i = 0; i < self->priv->syscfg_supported_modes->len; i++) { + MMHuaweiSyscfgCombination *single; + + single = &g_array_index (self->priv->syscfg_supported_modes, + MMHuaweiSyscfgCombination, + i); + if (single->allowed == allowed && single->preferred == preferred) { + found = single; + break; + } + } + + if (!found) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "Requested mode ^SYSCFG combination not found"); + return FALSE; + } + + command = g_strdup_printf ("^SYSCFG=%u,%u,40000000,2,4", + found->mode, + found->acqorder); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)set_current_modes_ready, + task); + g_free (command); + return TRUE; +} + +static gboolean +syscfgex_set_current_modes (MMBroadbandModemHuawei *self, + MMModemMode allowed, + MMModemMode preferred, + GTask *task, + GError **error) +{ + guint i; + MMHuaweiSyscfgexCombination *found = NULL; + gchar *command; + + for (i = 0; i < self->priv->syscfgex_supported_modes->len; i++) { + MMHuaweiSyscfgexCombination *single; + + single = &g_array_index (self->priv->syscfgex_supported_modes, + MMHuaweiSyscfgexCombination, + i); + if (single->allowed == allowed && single->preferred == preferred) { + found = single; + break; + } + } + + if (!found) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "Requested mode ^SYSCFGEX combination not found"); + return FALSE; + } + + command = g_strdup_printf ("^SYSCFGEX=\"%s\",3fffffff,2,4,7fffffffffffffff,,", + found->mode_str); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)set_current_modes_ready, + task); + g_free (command); + return TRUE; +} + +static void +set_current_modes (MMIfaceModem *_self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GTask *task; + GError *error = NULL; + + task = g_task_new (self, NULL, callback, user_data); + + if (self->priv->syscfgex_support == FEATURE_SUPPORTED) + syscfgex_set_current_modes (self, allowed, preferred, task, &error); + else if (self->priv->syscfg_support == FEATURE_SUPPORTED) + syscfg_set_current_modes (self, allowed, preferred, task, &error); + else if (self->priv->prefmode_support == FEATURE_SUPPORTED) + prefmode_set_current_modes (self, allowed, preferred, task, &error); + else + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Setting current modes is not supported"); + + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + } +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static void +huawei_signal_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + guint quality = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &quality)) + return; + + if (quality == 99) { + /* 99 means unknown */ + quality = 0; + } else { + /* Normalize the quality */ + quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31; + } + + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); +} + +static void +huawei_mode_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + gchar *str; + gint a; + guint32 mask = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + + str = g_match_info_fetch (match_info, 1); + a = atoi (str); + g_free (str); + + /* CDMA/EVDO devices may not send this */ + str = g_match_info_fetch (match_info, 2); + if (str[0]) + act = huawei_sysinfo_submode_to_act (atoi (str)); + g_free (str); + + switch (a) { + case 3: + /* GSM/GPRS mode */ + if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN && + (act < MM_MODEM_ACCESS_TECHNOLOGY_GSM || + act > MM_MODEM_ACCESS_TECHNOLOGY_EDGE)) { + str = mm_modem_access_technology_build_string_from_mask (act); + mm_obj_warn (self, "unexpected access technology (%s) in GSM/GPRS mode", str); + g_free (str); + act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + } + mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK; + break; + + case 5: + /* WCDMA mode */ + if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN && + (act < MM_MODEM_ACCESS_TECHNOLOGY_UMTS || + act > MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS)) { + str = mm_modem_access_technology_build_string_from_mask (act); + mm_obj_warn (self, "unexpected access technology (%s) in WCDMA mode", str); + g_free (str); + act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + } + mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK; + break; + + case 2: + /* CDMA mode */ + if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN && + act != MM_MODEM_ACCESS_TECHNOLOGY_1XRTT) { + str = mm_modem_access_technology_build_string_from_mask (act); + mm_obj_warn (self, "unexpected access technology (%s) in CDMA mode", str); + g_free (str); + act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + } + if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN) + act = MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; + mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK; + break; + + case 4: /* HDR mode */ + case 8: /* CDMA/HDR hybrid mode */ + if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN && + (act < MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 || + act > MM_MODEM_ACCESS_TECHNOLOGY_EVDOB)) { + str = mm_modem_access_technology_build_string_from_mask (act); + mm_obj_warn (self, "unexpected access technology (%s) in EVDO mode", str); + g_free (str); + act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + } + if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN) + act = MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; + mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK; + break; + + case 0: + act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + break; + + default: + mm_obj_warn (self, "unexpected mode change value reported: '%d'", a); + return; + } + + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), act, mask); +} + +static void +huawei_status_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + gchar *str; + gint n1, n2, n3, n4, n5, n6, n7; + + str = g_match_info_fetch (match_info, 1); + if (sscanf (str, "%x,%x,%x,%x,%x,%x,%x", &n1, &n2, &n3, &n4, &n5, &n6, &n7)) + mm_obj_dbg (self, "duration: %d up: %d Kbps down: %d Kbps total: %d total: %d\n", + n1, n2 * 8 / 1000, n3 * 8 / 1000, n4 / 1024, n5 / 1024); + g_free (str); +} + +typedef struct { + gboolean ipv4_available; + gboolean ipv4_connected; + gboolean ipv6_available; + gboolean ipv6_connected; +} NdisstatResult; + +static void +bearer_report_connection_status (MMBaseBearer *bearer, + NdisstatResult *ndisstat_result) +{ + if (ndisstat_result->ipv4_available) { + /* TODO: MMBroadbandBearerHuawei does not currently support IPv6. + * When it does, we should check the IP family associated with each bearer. */ + mm_base_bearer_report_connection_status (bearer, + ndisstat_result->ipv4_connected ? + MM_BEARER_CONNECTION_STATUS_CONNECTED : + MM_BEARER_CONNECTION_STATUS_DISCONNECTED); + } +} + +static void +huawei_ndisstat_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + gchar *str; + NdisstatResult ndisstat_result; + GError *error = NULL; + MMBearerList *list = NULL; + + str = g_match_info_fetch (match_info, 1); + if (!mm_huawei_parse_ndisstatqry_response (str, + &ndisstat_result.ipv4_available, + &ndisstat_result.ipv4_connected, + &ndisstat_result.ipv6_available, + &ndisstat_result.ipv6_connected, + &error)) { + mm_obj_dbg (self, "ignored invalid ^NDISSTAT unsolicited message '%s': %s", + str, error->message); + g_error_free (error); + g_free (str); + return; + } + g_free (str); + + mm_obj_dbg (self, "NDIS status: IPv4 %s, IPv6 %s", + ndisstat_result.ipv4_available ? + (ndisstat_result.ipv4_connected ? "connected" : "disconnected") : "not available", + ndisstat_result.ipv6_available ? + (ndisstat_result.ipv6_connected ? "connected" : "disconnected") : "not available"); + + /* If empty bearer list, nothing else to do */ + g_object_get (self, + MM_IFACE_MODEM_BEARER_LIST, &list, + NULL); + if (!list) + return; + + mm_bearer_list_foreach (list, + (MMBearerListForeachFunc)bearer_report_connection_status, + &ndisstat_result); + + g_object_unref (list); +} + +static void +detailed_signal_clear (DetailedSignal *signal) +{ + g_clear_object (&signal->cdma); + g_clear_object (&signal->evdo); + g_clear_object (&signal->gsm); + g_clear_object (&signal->umts); + g_clear_object (&signal->lte); +} + +static gboolean +get_rssi_dbm (guint rssi, gdouble *out_val) +{ + if (rssi <= 96) { + *out_val = (double) (-121.0 + rssi); + return TRUE; + } + return FALSE; +} + +static gboolean +get_ecio_db (guint ecio, gdouble *out_val) +{ + if (ecio <= 65) { + *out_val = -32.5 + ((double) ecio / 2.0); + return TRUE; + } + return FALSE; +} + +static gboolean +get_rsrp_dbm (guint rsrp, gdouble *out_val) +{ + if (rsrp <= 97) { + *out_val = (double) (-141.0 + rsrp); + return TRUE; + } + return FALSE; +} + +static gboolean +get_sinr_db (guint sinr, gdouble *out_val) +{ + if (sinr <= 251) { + *out_val = -20.2 + (double) (sinr / 5.0); + return TRUE; + } + return FALSE; +} + +static gboolean +get_rsrq_db (guint rsrq, gdouble *out_val) +{ + if (rsrq <= 34) { + *out_val = -20 + (double) (rsrq / 2.0); + return TRUE; + } + return FALSE; +} + +static void +huawei_hcsq_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + gchar *str; + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + guint value1 = 0; + guint value2 = 0; + guint value3 = 0; + guint value4 = 0; + guint value5 = 0; + gdouble v; + GError *error = NULL; + + str = g_match_info_fetch (match_info, 1); + if (!mm_huawei_parse_hcsq_response (str, + &act, + &value1, + &value2, + &value3, + &value4, + &value5, + &error)) { + mm_obj_dbg (self, "ignored invalid ^HCSQ message '%s': %s", str, error->message); + g_error_free (error); + g_free (str); + return; + } + g_free (str); + + detailed_signal_clear (&self->priv->detailed_signal); + + /* 2G */ + if (act == MM_MODEM_ACCESS_TECHNOLOGY_GSM) { + self->priv->detailed_signal.gsm = mm_signal_new (); + /* value1: gsm_rssi */ + if (get_rssi_dbm (value1, &v)) + mm_signal_set_rssi (self->priv->detailed_signal.gsm, v); + return; + } + + /* 3G */ + if (act == MM_MODEM_ACCESS_TECHNOLOGY_UMTS) { + self->priv->detailed_signal.umts = mm_signal_new (); + /* value1: wcdma_rssi */ + if (get_rssi_dbm (value1, &v)) + mm_signal_set_rssi (self->priv->detailed_signal.umts, v); + /* value2: wcdma_rscp; unused */ + /* value3: wcdma_ecio */ + if (get_ecio_db (value3, &v)) + mm_signal_set_ecio (self->priv->detailed_signal.umts, v); + return; + } + + /* 4G */ + if (act == MM_MODEM_ACCESS_TECHNOLOGY_LTE) { + self->priv->detailed_signal.lte = mm_signal_new (); + /* value1: lte_rssi */ + if (get_rssi_dbm (value1, &v)) + mm_signal_set_rssi (self->priv->detailed_signal.lte, v); + /* value2: lte_rsrp */ + if (get_rsrp_dbm (value2, &v)) + mm_signal_set_rsrp (self->priv->detailed_signal.lte, v); + /* value3: lte_sinr -> SNR? */ + if (get_sinr_db (value3, &v)) + mm_signal_set_snr (self->priv->detailed_signal.lte, v); + /* value4: lte_rsrq */ + if (get_rsrq_db (value4, &v)) + mm_signal_set_rsrq (self->priv->detailed_signal.lte, v); + return; + } + + /* CDMA and EVDO not yet supported */ +} + +static void +set_3gpp_unsolicited_events_handlers (MMBroadbandModemHuawei *self, + gboolean enable) +{ + GList *ports, *l; + + ports = mm_broadband_modem_huawei_get_at_port_list (self); + + /* Enable/disable unsolicited events in given port */ + for (l = ports; l; l = g_list_next (l)) { + MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data); + + /* Signal quality related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->rssi_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_signal_changed : NULL, + enable ? self : NULL, + NULL); + + /* Access technology related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->mode_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_mode_changed : NULL, + enable ? self : NULL, + NULL); + + /* Connection status related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->dsflowrpt_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_status_changed : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->ndisstat_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_ndisstat_changed : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->hcsq_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_hcsq_changed : NULL, + enable ? self : NULL, + NULL); + } + + g_list_free_full (ports, g_object_unref); +} + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_3gpp_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_3gpp_setup_unsolicited_events_ready, + task); +} + +static void +parent_3gpp_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Our own cleanup first */ + set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_3gpp_cleanup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Enabling unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +own_enable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static const MMBaseModemAtCommand unsolicited_enable_sequence[] = { + /* With ^PORTSEL we specify whether we want the PCUI port (0) or the + * modem port (1) to receive the unsolicited messages */ + { "^PORTSEL=0", 5, FALSE, NULL }, + { "^CURC=1", 3, FALSE, NULL }, + { NULL } +}; + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + } + + /* Our own enable now */ + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_enable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_enable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's enable */ + iface_modem_3gpp_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Disabling unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +own_disable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_full_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Next, chain up parent's disable */ + iface_modem_3gpp_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Our own disable first */ + mm_base_modem_at_command_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + "^CURC=0", + 5, + FALSE, /* allow_cached */ + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_disable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +huawei_modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_huawei_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer; + GError *error = NULL; + + bearer = mm_broadband_bearer_huawei_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + g_object_unref (task); +} + +static void +broadband_bearer_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer; + GError *error = NULL; + + bearer = mm_broadband_bearer_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + g_object_unref (task); +} + +static void +create_bearer_for_net_port (GTask *task) +{ + MMBroadbandModemHuawei *self; + MMBearerProperties *properties; + + self = g_task_get_source_object (task); + properties = g_task_get_task_data (task); + + switch (self->priv->ndisdup_support) { + case FEATURE_NOT_SUPPORTED: + mm_obj_dbg (self, "^NDISDUP not supported, creating default bearer..."); + mm_broadband_bearer_new (MM_BROADBAND_MODEM (self), + properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_new_ready, + task); + return; + case FEATURE_SUPPORTED: + mm_obj_dbg (self, "^NDISDUP supported, creating huawei bearer..."); + mm_broadband_bearer_huawei_new (MM_BROADBAND_MODEM_HUAWEI (self), + properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_huawei_new_ready, + task); + return; + case FEATURE_SUPPORT_UNKNOWN: + default: + g_assert_not_reached (); + } +} + +static MMPortSerialAt * +peek_port_at_for_data (MMBroadbandModemHuawei *self, + MMPort *port) +{ + GList *cdc_wdm_at_ports, *l; + const gchar *net_port_parent_path; + MMPortSerialAt *found = NULL; + + g_warn_if_fail (mm_port_get_subsys (port) == MM_PORT_SUBSYS_NET); + net_port_parent_path = mm_kernel_device_get_interface_sysfs_path (mm_port_peek_kernel_device (port)); + if (!net_port_parent_path) { + mm_obj_warn (self, "no parent path for net port %s", mm_port_get_device (port)); + return NULL; + } + + /* Find the CDC-WDM port on the same USB interface as the given net port */ + cdc_wdm_at_ports = mm_base_modem_find_ports (MM_BASE_MODEM (self), + MM_PORT_SUBSYS_USBMISC, + MM_PORT_TYPE_AT); + for (l = cdc_wdm_at_ports; l && !found; l = g_list_next (l)) { + const gchar *wdm_port_parent_path; + + g_assert (MM_IS_PORT_SERIAL_AT (l->data)); + wdm_port_parent_path = mm_kernel_device_get_interface_sysfs_path (mm_port_peek_kernel_device (MM_PORT (l->data))); + if (wdm_port_parent_path && g_str_equal (wdm_port_parent_path, net_port_parent_path)) + found = MM_PORT_SERIAL_AT (l->data); + } + + g_list_free_full (cdc_wdm_at_ports, g_object_unref); + return found; +} + + +MMPortSerialAt * +mm_broadband_modem_huawei_peek_port_at_for_data (MMBroadbandModemHuawei *self, + MMPort *port) +{ + MMPortSerialAt *found; + + g_assert (self->priv->ndisdup_support == FEATURE_SUPPORTED); + + found = peek_port_at_for_data (self, port); + if (!found) + mm_obj_dbg (self, "couldn't find associated cdc-wdm port for %s", mm_port_get_device (port)); + return found; +} + +static void +ensure_ndisdup_support_checked (MMBroadbandModemHuawei *self, + MMPort *port) +{ + /* Check NDISDUP support the first time we need it */ + if (self->priv->ndisdup_support != FEATURE_SUPPORT_UNKNOWN) + return; + + /* First, check for devices which support NDISDUP on any AT port. These + * devices are tagged by udev */ + if (mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_HUAWEI_NDISDUP_SUPPORTED")) { + mm_obj_dbg (self, "^NDISDUP is supported"); + self->priv->ndisdup_support = FEATURE_SUPPORTED; + } + /* Then, look for devices which have both a net port and a cdc-wdm + * AT-capable port. We assume that these devices allow NDISDUP only + * when issued in the cdc-wdm port. */ + else if (peek_port_at_for_data (self, port)) { + mm_obj_dbg (self, "^NDISDUP is supported on non-serial AT port"); + self->priv->ndisdup_support = FEATURE_SUPPORTED; + } + + if (self->priv->ndisdup_support != FEATURE_SUPPORT_UNKNOWN) + return; + + mm_obj_dbg (self, "^NDISDUP is not supported"); + self->priv->ndisdup_support = FEATURE_NOT_SUPPORTED; +} + +static void +huawei_modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMPort *port; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, g_object_ref (properties), g_object_unref); + + port = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET); + if (port) { + ensure_ndisdup_support_checked (MM_BROADBAND_MODEM_HUAWEI (self), port); + create_bearer_for_net_port (task); + return; + } + + mm_obj_dbg (self, "creating default bearer..."); + mm_broadband_bearer_new (MM_BROADBAND_MODEM (self), + properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_new_ready, + task); +} + +/*****************************************************************************/ +/* USSD encode/decode (3GPP-USSD interface) + * + * Huawei devices don't use the current charset (as per AT+CSCS) in the CUSD + * command, they instead expect data encoded in GSM-7 already, given as a + * hex string. + */ + +static gchar * +encode (MMIfaceModem3gppUssd *self, + const gchar *command, + guint *scheme, + GError **error) +{ + g_autoptr(GByteArray) gsm = NULL; + g_autofree guint8 *packed = NULL; + guint32 packed_len = 0; + + gsm = mm_modem_charset_bytearray_from_utf8 (command, MM_MODEM_CHARSET_GSM, FALSE, error); + if (!gsm) + return NULL; + + *scheme = MM_MODEM_GSM_USSD_SCHEME_7BIT; + + /* If command is a multiple of 7 characters long, Huawei firmwares + * apparently want that padded. Maybe all modems? + */ + if (gsm->len % 7 == 0) { + static const guint8 padding = 0x0d; + + g_byte_array_append (gsm, &padding, 1); + } + + packed = mm_charset_gsm_pack (gsm->data, gsm->len, 0, &packed_len); + return mm_utils_bin2hexstr (packed, packed_len); +} + +static gchar * +decode (MMIfaceModem3gppUssd *self, + const gchar *reply, + GError **error) +{ + g_autofree guint8 *bin = NULL; + gsize bin_len = 0; + g_autofree guint8 *unpacked = NULL; + guint32 unpacked_len; + g_autoptr(GByteArray) unpacked_array = NULL; + + bin = mm_utils_hexstr2bin (reply, -1, &bin_len, error); + if (!bin) + return NULL; + + unpacked = mm_charset_gsm_unpack (bin, (bin_len * 8) / 7, 0, &unpacked_len); + /* if the last character in a 7-byte block is padding, then drop it */ + if ((bin_len % 7 == 0) && (unpacked[unpacked_len - 1] == 0x0d)) + unpacked_len--; + + unpacked_array = g_byte_array_sized_new (unpacked_len); + g_byte_array_append (unpacked_array, unpacked, unpacked_len); + + return mm_modem_charset_bytearray_to_utf8 (unpacked_array, MM_MODEM_CHARSET_GSM, FALSE, error); +} + +/*****************************************************************************/ + +static void +huawei_1x_signal_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + guint quality = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &quality)) + return; + + quality = MM_CLAMP_HIGH (quality, 100); + mm_obj_dbg (self, "1X signal quality: %u", quality); + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); +} + +static void +huawei_evdo_signal_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + guint quality = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &quality)) + return; + + quality = MM_CLAMP_HIGH (quality, 100); + mm_obj_dbg (self, "EVDO signal quality: %u", quality); + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); +} + +/* Signal quality loading (Modem interface) */ + +static guint +modem_load_signal_quality_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return 0; + } + return (guint)value; +} + +static void +parent_load_signal_quality_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + guint signal_quality; + + signal_quality = iface_modem_parent->load_signal_quality_finish (self, res, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_int (task, signal_quality); + g_object_unref (task); +} + +static void +signal_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response, *command; + gchar buf[5]; + guint quality = 0, i = 0; + + response = mm_base_modem_at_command_finish (self, res, NULL); + if (!response) { + /* Fallback to parent's method */ + iface_modem_parent->load_signal_quality ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_signal_quality_ready, + task); + return; + } + + command = g_task_get_task_data (task); + g_assert (command); + response = mm_strip_tag (response, command); + /* 'command' won't include the trailing ':' in the response, so strip that */ + while ((*response == ':') || isspace (*response)) + response++; + + /* Sanitize response for mm_get_uint_from_str() which wants only digits */ + memset (buf, 0, sizeof (buf)); + while (i < (sizeof (buf) - 1) && isdigit (*response)) + buf[i++] = *response++; + + if (mm_get_uint_from_str (buf, &quality)) { + quality = MM_CLAMP_HIGH (quality, 100); + g_task_return_int (task, quality); + } else { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse %s response: '%s'", + command, response); + } + + g_object_unref (task); +} + +static void +modem_load_signal_quality (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + const char *command = "^CSQLVL"; + + task = g_task_new (self, NULL, callback, user_data); + + /* 3GPP modems can just run parent's signal quality loading */ + if (mm_iface_modem_is_3gpp (self)) { + iface_modem_parent->load_signal_quality ( + self, + (GAsyncReadyCallback)parent_load_signal_quality_ready, + task); + return; + } + + /* CDMA modems need custom signal quality loading */ + + g_object_get (G_OBJECT (self), + MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE, &evdo_state, + NULL); + if (evdo_state > MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) + command = "^HDRCSQLVL"; + + g_task_set_task_data (task, g_strdup (command), g_free); + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)signal_ready, + task); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (CDMA interface) */ + +static void +set_cdma_unsolicited_events_handlers (MMBroadbandModemHuawei *self, + gboolean enable) +{ + GList *ports, *l; + + ports = mm_broadband_modem_huawei_get_at_port_list (self); + + /* Enable/disable unsolicited events in given port */ + for (l = ports; l; l = g_list_next (l)) { + MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data); + + /* Signal quality related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->rssilvl_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_1x_signal_changed : NULL, + enable ? self : NULL, + NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->hrssilvl_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_evdo_signal_changed : NULL, + enable ? self : NULL, + NULL); + /* Access technology related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->mode_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_mode_changed : NULL, + enable ? self : NULL, + NULL); + } + + g_list_free_full (ports, g_object_unref); +} + +static gboolean +modem_cdma_setup_cleanup_unsolicited_events_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_cdma_setup_unsolicited_events_ready (MMIfaceModemCdma *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_cdma_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_cdma_setup_unsolicited_events (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's setup if needed */ + if (iface_modem_cdma_parent->setup_unsolicited_events && + iface_modem_cdma_parent->setup_unsolicited_events_finish) { + iface_modem_cdma_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cdma_setup_unsolicited_events_ready, + task); + return; + } + + /* Otherwise just run our setup and complete */ + set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_cdma_cleanup_unsolicited_events_ready (MMIfaceModemCdma *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_cdma_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_cdma_cleanup_unsolicited_events (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Our own cleanup first */ + set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE); + + /* Chain up parent's setup if needed */ + if (iface_modem_cdma_parent->cleanup_unsolicited_events && + iface_modem_cdma_parent->cleanup_unsolicited_events_finish) { + iface_modem_cdma_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cdma_cleanup_unsolicited_events_ready, + task); + return; + } + + /* Otherwise we're done */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Setup registration checks (CDMA interface) */ + +typedef struct { + gboolean skip_qcdm_call_manager_step; + gboolean skip_qcdm_hdr_step; + gboolean skip_at_cdma_service_status_step; + gboolean skip_at_cdma1x_serving_system_step; + gboolean skip_detailed_registration_state; +} SetupRegistrationChecksResults; + +static gboolean +setup_registration_checks_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + gboolean *skip_qcdm_call_manager_step, + gboolean *skip_qcdm_hdr_step, + gboolean *skip_at_cdma_service_status_step, + gboolean *skip_at_cdma1x_serving_system_step, + gboolean *skip_detailed_registration_state, + GError **error) +{ + SetupRegistrationChecksResults *results; + + results = g_task_propagate_pointer (G_TASK (res), error); + if (!results) + return FALSE; + + *skip_qcdm_call_manager_step = results->skip_qcdm_call_manager_step; + *skip_qcdm_hdr_step = results->skip_qcdm_hdr_step; + *skip_at_cdma_service_status_step = results->skip_at_cdma_service_status_step; + *skip_at_cdma1x_serving_system_step = results->skip_at_cdma1x_serving_system_step; + *skip_detailed_registration_state = results->skip_detailed_registration_state; + g_free (results); + return TRUE; +} + +static void +parent_setup_registration_checks_ready (MMIfaceModemCdma *self, + GAsyncResult *res, + GTask *task) +{ + SetupRegistrationChecksResults *results; + GError *error = NULL; + + results = g_new0 (SetupRegistrationChecksResults, 1); + + if (!iface_modem_cdma_parent->setup_registration_checks_finish (self, + res, + &results->skip_qcdm_call_manager_step, + &results->skip_qcdm_hdr_step, + &results->skip_at_cdma_service_status_step, + &results->skip_at_cdma1x_serving_system_step, + &results->skip_detailed_registration_state, + &error)) { + g_free (results); + g_task_return_error (task, error); + } else { + gboolean evdo_supported = FALSE; + + g_object_get (self, + MM_IFACE_MODEM_CDMA_EVDO_NETWORK_SUPPORTED, &evdo_supported, + NULL); + + /* Don't use AT+CSS on EVDO-capable hardware for determining registration + * status, because often the device will have only an EVDO connection and + * AT+CSS won't necessarily report EVDO registration status, only 1X. + */ + if (evdo_supported) + results->skip_at_cdma1x_serving_system_step = TRUE; + + /* Force to always use the detailed registration checks, as we have + * ^SYSINFO for that */ + results->skip_detailed_registration_state = FALSE; + + g_task_return_pointer (task, results, g_free); + } + + g_object_unref (task); +} + +static void +setup_registration_checks (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Run parent's checks first */ + iface_modem_cdma_parent->setup_registration_checks (self, + (GAsyncReadyCallback)parent_setup_registration_checks_ready, + task); +} + +/*****************************************************************************/ +/* Detailed registration state (CDMA interface) */ + +typedef struct { + MMModemCdmaRegistrationState detailed_cdma1x_state; + MMModemCdmaRegistrationState detailed_evdo_state; +} DetailedRegistrationStateResults; + +typedef struct { + DetailedRegistrationStateResults state; +} DetailedRegistrationStateContext; + +static gboolean +get_detailed_registration_state_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + MMModemCdmaRegistrationState *detailed_cdma1x_state, + MMModemCdmaRegistrationState *detailed_evdo_state, + GError **error) +{ + DetailedRegistrationStateResults *results; + + results = g_task_propagate_pointer (G_TASK (res), error); + if (!results) + return FALSE; + + *detailed_cdma1x_state = results->detailed_cdma1x_state; + *detailed_evdo_state = results->detailed_evdo_state; + g_free (results); + return TRUE; +} + +static void +registration_state_sysinfo_ready (MMBroadbandModemHuawei *self, + GAsyncResult *res, + GTask *task) +{ + DetailedRegistrationStateContext *ctx; + gboolean extended = FALSE; + guint srv_status = 0; + guint sys_mode = 0; + guint roam_status = 0; + + ctx = g_task_get_task_data (task); + + if (!sysinfo_finish (self, + res, + &extended, + &srv_status, + NULL, /* srv_domain */ + &roam_status, + NULL, /* sim_state */ + &sys_mode, + NULL, /* sys_submode_valid */ + NULL, /* sys_submode */ + NULL)) { + /* If error, leave superclass' reg state alone if ^SYSINFO isn't supported. */ + g_task_return_pointer (task, + g_memdup (&ctx->state, sizeof (ctx->state)), + g_free); + g_object_unref (task); + return; + } + + if (srv_status == 2) { + MMModemCdmaRegistrationState reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + MMModemAccessTechnology act; + gboolean cdma1x = FALSE; + gboolean evdo = FALSE; + + /* Service available, check roaming state */ + if (roam_status == 0) + reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME; + else if (roam_status == 1) + reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING; + + /* Check service type */ + act = (extended ? + huawei_sysinfoex_mode_to_act (sys_mode): + huawei_sysinfo_mode_to_act (sys_mode)); + + if (act & MM_MODEM_ACCESS_TECHNOLOGY_1XRTT) { + cdma1x = TRUE; + ctx->state.detailed_cdma1x_state = reg_state; + } + + if (act & MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 || + act & MM_MODEM_ACCESS_TECHNOLOGY_EVDOA || + act & MM_MODEM_ACCESS_TECHNOLOGY_EVDOB) { + evdo = TRUE; + ctx->state.detailed_evdo_state = reg_state; + } + + if (!cdma1x && !evdo) { + /* Say we're registered to something even though sysmode parsing failed */ + mm_obj_dbg (self, "assuming registered at least in CDMA1x"); + ctx->state.detailed_cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + } + } + + g_task_return_pointer (task, + g_memdup (&ctx->state, sizeof (ctx->state)), + g_free); + g_object_unref (task); +} + +static void +get_detailed_registration_state (MMIfaceModemCdma *self, + MMModemCdmaRegistrationState cdma1x_state, + MMModemCdmaRegistrationState evdo_state, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DetailedRegistrationStateContext *ctx; + GTask *task; + + /* Setup context */ + ctx = g_new (DetailedRegistrationStateContext, 1); + ctx->state.detailed_cdma1x_state = cdma1x_state; + ctx->state.detailed_evdo_state = evdo_state; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + sysinfo (MM_BROADBAND_MODEM_HUAWEI (self), + (GAsyncReadyCallback)registration_state_sysinfo_ready, + task); +} + +/*****************************************************************************/ +/* Check if Voice supported (Voice interface) */ + +static gboolean +modem_voice_check_support_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +voice_parent_check_support_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + gboolean parent_support; + + parent_support = iface_modem_voice_parent->check_support_finish (self, res, NULL); + g_task_return_boolean (task, parent_support); + g_object_unref (task); +} + +static void +cvoice_check_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response || + !mm_huawei_parse_cvoice_response (response, + &self->priv->audio_hz, + &self->priv->audio_bits, + &error)) { + self->priv->cvoice_support = FEATURE_NOT_SUPPORTED; + mm_obj_dbg (self, "CVOICE is unsupported: %s", error->message); + g_clear_error (&error); + + /* Now check generic support */ + iface_modem_voice_parent->check_support (MM_IFACE_MODEM_VOICE (self), + (GAsyncReadyCallback)voice_parent_check_support_ready, + task); + return; + } + + mm_obj_dbg (self, "CVOICE is supported"); + self->priv->cvoice_support = FEATURE_SUPPORTED; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_voice_check_support (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + /* Check for Huawei-specific ^CVOICE support */ + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^CVOICE?", + 3, + TRUE, + (GAsyncReadyCallback)cvoice_check_ready, + task); +} + +/*****************************************************************************/ +/* In-call audio channel setup/cleanup */ + +static gboolean +modem_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +modem_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* If there is no CVOICE support, no custom audio setup required + * (i.e. audio path is externally managed) */ + if (self->priv->cvoice_support == FEATURE_SUPPORTED) { + MMPort *port; + + /* The QCDM port, if present, switches back from voice to QCDM after + * the voice call is dropped. */ + port = MM_PORT (mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self))); + if (port) { + /* During a voice call, we'll set the QCDM port as connected, and that + * will make us ignore all incoming data and avoid sending any outgoing + * data. */ + mm_port_set_connected (port, FALSE); + } + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static gboolean +modem_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *_self, + GAsyncResult *res, + MMPort **audio_port, + MMCallAudioFormat **audio_format, + GError **error) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + + if (!g_task_propagate_boolean (G_TASK (res), error)) + return FALSE; + + if (self->priv->cvoice_support == FEATURE_SUPPORTED) { + MMPort *port; + + /* Setup audio format */ + if (audio_format) { + gchar *resolution_str; + + resolution_str = g_strdup_printf ("s%ule", self->priv->audio_bits); + *audio_format = mm_call_audio_format_new (); + mm_call_audio_format_set_encoding (*audio_format, "pcm"); + mm_call_audio_format_set_resolution (*audio_format, resolution_str); + mm_call_audio_format_set_rate (*audio_format, self->priv->audio_hz); + g_free (resolution_str); + } + + /* The QCDM port, if present, switches from QCDM to voice while + * a voice call is active. */ + port = MM_PORT (mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self))); + if (port) { + /* During a voice call, we'll set the QCDM port as connected, and that + * will make us ignore all incoming data and avoid sending any outgoing + * data. */ + mm_port_set_connected (port, TRUE); + } + + if (audio_port) + *audio_port = (port ? g_object_ref (port) : NULL);; + } else { + if (audio_format) + *audio_format = NULL; + if (audio_port) + *audio_port = NULL; + } + + return TRUE; +} + +static void +ddsetex_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_voice_setup_in_call_audio_channel (MMIfaceModemVoice *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* If there is no CVOICE support, no custom audio setup required + * (i.e. audio path is externally managed) */ + if (self->priv->cvoice_support != FEATURE_SUPPORTED) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Enable audio streaming on the audio port */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^DDSETEX=2", + 5, + FALSE, + (GAsyncReadyCallback)ddsetex_ready, + task); +} + +/*****************************************************************************/ +/* Common setup/cleanup voice unsolicited events */ + +typedef enum { + HUAWEI_CALL_TYPE_VOICE = 0, + HUAWEI_CALL_TYPE_CS_DATA = 1, + HUAWEI_CALL_TYPE_PS_DATA = 2, + HUAWEI_CALL_TYPE_CDMA_SMS = 3, + HUAWEI_CALL_TYPE_OTA_STANDARD_OTASP = 7, + HUAWEI_CALL_TYPE_OTA_NON_STANDARD_OTASP = 8, + HUAWEI_CALL_TYPE_EMERGENCY = 9, +} HuaweiCallType; + +static void +orig_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + MMCallInfo call_info = { 0 }; + guint aux = 0; + + if (!mm_get_uint_from_match_info (match_info, 2, &aux)) { + mm_obj_warn (self, "couldn't parse call type from ^ORIG"); + return; + } + if (aux != HUAWEI_CALL_TYPE_VOICE && aux != HUAWEI_CALL_TYPE_EMERGENCY) { + mm_obj_dbg (self, "ignored ^ORIG for non-voice call"); + return; + } + + if (!mm_get_uint_from_match_info (match_info, 1, &aux)) { + mm_obj_warn (self, "couldn't parse call index from ^ORIG"); + return; + } + call_info.index = aux; + call_info.state = MM_CALL_STATE_DIALING; + call_info.direction = MM_CALL_DIRECTION_OUTGOING; + + mm_obj_dbg (self, "call %u state updated: dialing", call_info.index); + + mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); +} + +static void +conf_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + MMCallInfo call_info = { 0 }; + guint aux = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &aux)) { + mm_obj_warn (self, "couldn't parse call index from ^CONF"); + return; + } + call_info.index = aux; + call_info.state = MM_CALL_STATE_RINGING_OUT; + call_info.direction = MM_CALL_DIRECTION_OUTGOING; + + mm_obj_dbg (self, "call %u state updated: ringing-out", call_info.index); + + mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); +} + +static void +conn_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + MMCallInfo call_info = { 0 }; + guint aux = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &aux)) { + mm_obj_warn (self, "couldn't parse call index from ^CONN"); + return; + } + call_info.index = aux; + call_info.state = MM_CALL_STATE_ACTIVE; + call_info.direction = MM_CALL_DIRECTION_UNKNOWN; + + mm_obj_dbg (self, "call %u state updated: active", aux); + + mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); +} + +static void +cend_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + MMCallInfo call_info = { 0 }; + guint aux = 0; + + /* only index is mandatory */ + if (!mm_get_uint_from_match_info (match_info, 1, &aux)) { + mm_obj_warn (self, "couldn't parse call index from ^CEND"); + return; + } + call_info.index = aux; + call_info.state = MM_CALL_STATE_TERMINATED; + call_info.direction = MM_CALL_DIRECTION_UNKNOWN; + + mm_obj_dbg (self, "call %u state updated: terminated", call_info.index); + if (mm_get_uint_from_match_info (match_info, 2, &aux)) + mm_obj_dbg (self, " call duration: %u seconds", aux); + if (mm_get_uint_from_match_info (match_info, 3, &aux)) + mm_obj_dbg (self, " end status code: %u", aux); + if (mm_get_uint_from_match_info (match_info, 4, &aux)) + mm_obj_dbg (self, " call control cause: %u", aux); + + mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); +} + +static void +ddtmf_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHuawei *self) +{ + gchar *dtmf; + + dtmf = g_match_info_fetch (match_info, 1); + mm_obj_dbg (self, "received DTMF: %s", dtmf); + /* call index unknown */ + mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf); + g_free (dtmf); +} + +static void +common_voice_setup_cleanup_unsolicited_events (MMBroadbandModemHuawei *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + self->priv->orig_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)orig_received : NULL, + enable ? self : NULL, + NULL); + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + self->priv->conf_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)conf_received : NULL, + enable ? self : NULL, + NULL); + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + self->priv->conn_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)conn_received : NULL, + enable ? self : NULL, + NULL); + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + self->priv->cend_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)cend_received : NULL, + enable ? self : NULL, + NULL); + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + self->priv->ddtmf_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)ddtmf_received : NULL, + enable ? self : NULL, + NULL); + } +} + +/*****************************************************************************/ +/* Setup unsolicited events (Voice interface) */ + +static gboolean +modem_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Our own setup now */ + common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_HUAWEI (self), TRUE); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's setup */ + iface_modem_voice_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Cleanup unsolicited events (Voice interface) */ + +static gboolean +modem_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* cleanup our own */ + common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_HUAWEI (self), FALSE); + + /* Chain up parent's cleanup */ + iface_modem_voice_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Enabling unsolicited events (Voice interface) */ + +static gboolean +modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +own_voice_enable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static const MMBaseModemAtCommand unsolicited_voice_enable_sequence[] = { + /* With ^DDTMFCFG we active the DTMF Decoder */ + { "^DDTMFCFG=0,1", 3, FALSE, NULL }, + { NULL } +}; + +static void +parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Our own enable now */ + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_voice_enable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_voice_enable_unsolicited_events_ready, + task); +} + +static void +modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's enable */ + iface_modem_voice_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Disabling unsolicited events (Voice interface) */ + +static gboolean +modem_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +own_voice_disable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static const MMBaseModemAtCommand unsolicited_voice_disable_sequence[] = { + /* With ^DDTMFCFG we deactivate the DTMF Decoder */ + { "^DDTMFCFG=1,0", 3, FALSE, NULL }, + { NULL } +}; + +static void +parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* our own disable now */ + + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_voice_disable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_voice_disable_unsolicited_events_ready, + task); +} + +static void +modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's disable */ + iface_modem_voice_parent->disable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Create call (Voice interface) */ + +static MMBaseCall * +create_call (MMIfaceModemVoice *self, + MMCallDirection direction, + const gchar *number) +{ + return mm_base_call_new (MM_BASE_MODEM (self), + direction, + number, + TRUE, /* skip_incoming_timeout */ + TRUE, /* supports_dialing_to_ringing */ + TRUE); /* supports_ringing_to_active) */ +} + +/*****************************************************************************/ +/* Load network time (Time interface) */ + +static MMNetworkTimezone * +modem_time_load_network_timezone_finish (MMIfaceModemTime *_self, + GAsyncResult *res, + GError **error) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + MMNetworkTimezone *tz = NULL; + const gchar *response; + + g_assert (self->priv->nwtime_support == FEATURE_SUPPORTED || + self->priv->time_support == FEATURE_SUPPORTED); + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error); + if (!response) + return NULL; + + if (self->priv->nwtime_support == FEATURE_SUPPORTED) + mm_huawei_parse_nwtime_response (response, NULL, &tz, error); + else if (self->priv->time_support == FEATURE_SUPPORTED) + mm_huawei_parse_time_response (response, NULL, &tz, error); + return tz; +} + + +static gchar * +modem_time_load_network_time_finish (MMIfaceModemTime *_self, + GAsyncResult *res, + GError **error) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + const gchar *response; + gchar *iso8601 = NULL; + + g_assert (self->priv->nwtime_support == FEATURE_SUPPORTED || + self->priv->time_support == FEATURE_SUPPORTED); + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error); + if (!response) + return NULL; + + if (self->priv->nwtime_support == FEATURE_SUPPORTED) + mm_huawei_parse_nwtime_response (response, &iso8601, NULL, error); + else if (self->priv->time_support == FEATURE_SUPPORTED) + mm_huawei_parse_time_response (response, &iso8601, NULL, error); + return iso8601; +} + +static void +modem_time_load_network_time_or_zone (MMIfaceModemTime *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + const char *command = NULL; + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + + if (self->priv->nwtime_support == FEATURE_SUPPORTED) + command = "^NWTIME?"; + else if (self->priv->time_support == FEATURE_SUPPORTED) + command = "^TIME"; + + g_assert (command != NULL); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + command, + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Power state loading (Modem interface) */ + +static void +enable_disable_unsolicited_rfswitch_event_handler (MMBroadbandModemHuawei *self, + gboolean enable) +{ + GList *ports, *l; + + ports = mm_broadband_modem_huawei_get_at_port_list (self); + + mm_obj_dbg (self, "%s ^RFSWITCH unsolicited event handler", + enable ? "enable" : "disable"); + + for (l = ports; l; l = g_list_next (l)) { + MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data); + + mm_port_serial_at_enable_unsolicited_msg_handler ( + port, + self->priv->rfswitch_regex, + enable); + } + + g_list_free_full (ports, g_object_unref); +} + +static void +parent_load_power_state_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + MMModemPowerState power_state; + + power_state = iface_modem_parent->load_power_state_finish (self, res, &error); + if (error) + g_task_return_error (task, error); + else { + /* As modem_power_down uses +CFUN=0 to put the modem in low state, we treat + * CFUN 0 as 'LOW' power state instead of 'OFF'. Otherwise, MMIfaceModem + * would prevent the modem from transitioning back to the 'ON' power state. */ + if (power_state == MM_MODEM_POWER_STATE_OFF) + power_state = MM_MODEM_POWER_STATE_LOW; + + g_task_return_int (task, power_state); + } + + g_object_unref (task); +} + +static void +huawei_rfswitch_check_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GError *error = NULL; + const gchar *response; + gint sw_state; + + enable_disable_unsolicited_rfswitch_event_handler (MM_BROADBAND_MODEM_HUAWEI (self), + TRUE /* enable */); + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (response) { + response = mm_strip_tag (response, "^RFSWITCH:"); + if (sscanf (response, "%d", &sw_state) != 1 || + (sw_state != 0 && sw_state != 1)) { + mm_obj_warn (self, "couldn't parse ^RFSWITCH response '%s'", response); + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse ^RFSWITCH response '%s'", + response); + } + } + + if (self->priv->rfswitch_support == FEATURE_SUPPORT_UNKNOWN) { + if (error) { + mm_obj_dbg (self, "^RFSWITCH is not supported"); + self->priv->rfswitch_support = FEATURE_NOT_SUPPORTED; + g_error_free (error); + /* Fall back to parent's load_power_state */ + iface_modem_parent->load_power_state (MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_power_state_ready, + task); + return; + } + + mm_obj_dbg (self, "^RFSWITCH is supported"); + self->priv->rfswitch_support = FEATURE_SUPPORTED; + } + + if (error) + g_task_return_error (task, error); + else + g_task_return_int (task, + sw_state ? MM_MODEM_POWER_STATE_ON : MM_MODEM_POWER_STATE_LOW); + + g_object_unref (task); +} + +static MMModemPowerState +load_power_state_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_POWER_STATE_UNKNOWN; + } + return (MMModemPowerState)value; +} + +static void +load_power_state (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) { + case FEATURE_SUPPORT_UNKNOWN: + case FEATURE_SUPPORTED: { + /* Temporarily disable the unsolicited ^RFSWITCH event handler in order to + * prevent it from discarding the response to the ^RFSWITCH? command. + * It will be re-enabled in huawei_rfswitch_check_ready. + */ + enable_disable_unsolicited_rfswitch_event_handler (MM_BROADBAND_MODEM_HUAWEI (self), + FALSE /* enable */); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^RFSWITCH?", + 3, + FALSE, + (GAsyncReadyCallback)huawei_rfswitch_check_ready, + task); + break; + } + case FEATURE_NOT_SUPPORTED: + /* Run parent's load_power_state */ + iface_modem_parent->load_power_state (self, + (GAsyncReadyCallback)parent_load_power_state_ready, + task); + break; + default: + g_assert_not_reached (); + break; + } +} + +/*****************************************************************************/ +/* Modem power up (Modem interface) */ + +static gboolean +huawei_modem_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +huawei_modem_power_up (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) { + case FEATURE_NOT_SUPPORTED: + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=1", + 30, + FALSE, + callback, + user_data); + break; + case FEATURE_SUPPORTED: + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^RFSWITCH=1", + 30, + FALSE, + callback, + user_data); + break; + case FEATURE_SUPPORT_UNKNOWN: + default: + g_assert_not_reached (); + break; + } +} + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +huawei_modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +huawei_modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) { + case FEATURE_NOT_SUPPORTED: + /* +CFUN=0 is supported on all Huawei modems but +CFUN=4 isn't, + * thus we use +CFUN=0 to put the modem in low power state. */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=0", + 30, + FALSE, + callback, + user_data); + break; + case FEATURE_SUPPORTED: + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^RFSWITCH=0", + 30, + FALSE, + callback, + user_data); + break; + case FEATURE_SUPPORT_UNKNOWN: + default: + g_assert_not_reached (); + break; + } +} + +/*****************************************************************************/ +/* Create SIM (Modem interface) */ + +static MMBaseSim * +huawei_modem_create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_huawei_new_finish (res, error); +} + +static void +huawei_modem_create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* New Sierra SIM */ + mm_sim_huawei_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + + +/*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +static MMModemLocationSource +location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + + sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* not sure how to check if GPS is supported, just allow it */ + if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) + sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED); + + /* So we're done, complete */ + g_task_return_int (task, sources); + g_object_unref (task); +} + +static void +location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's setup */ + iface_modem_location_parent->load_capabilities (self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + task); +} + +/*****************************************************************************/ +/* Disable location gathering (Location interface) */ + +static gboolean +disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +disable_location_gathering (MMIfaceModemLocation *_self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GTask *task; + + /* NOTE: no parent disable_location_gathering() implementation */ + + task = g_task_new (self, NULL, callback, user_data); + + self->priv->enabled_sources &= ~source; + + /* Only stop GPS engine if no GPS-related sources enabled */ + if ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) && + !(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) { + MMPortSerialGps *gps_port; + + /* Close the data port if we don't need it anymore */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_RAW | MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) { + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + mm_base_modem_at_command (MM_BASE_MODEM (_self), + "^WPEND", + 3, + FALSE, + (GAsyncReadyCallback)gps_disabled_ready, + task); + return; + } + + /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +static const MMBaseModemAtCommand gps_startup[] = { + { "^WPDOM=0", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, + { "^WPDST=1", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, + { "^WPDFR=65535,30", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, + { "^WPDGP", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, + { NULL } +}; + +static gboolean +enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_startup_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + MMModemLocationSource source; + GError *error = NULL; + + mm_base_modem_at_sequence_finish (_self, res, NULL, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + source = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + /* Only open the GPS port in NMEA/RAW setups */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMPortSerialGps *gps_port; + + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (!gps_port || !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't open raw GPS serial port"); + } else { + /* GPS port was successfully opened */ + self->priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + } + } else { + /* No need to open GPS port */ + self->priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + } + + g_object_unref (task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GError *error = NULL; + MMModemLocationSource source; + gboolean start_gps = FALSE; + + if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Now our own enabling */ + + source = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + /* Only start GPS engine if not done already */ + start_gps = ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) && + !(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))); + + if (start_gps) { + mm_base_modem_at_sequence ( + MM_BASE_MODEM (self), + gps_startup, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + (GAsyncReadyCallback)gps_startup_ready, + task); + return; + } + + /* For any other location (e.g. 3GPP), or if GPS already running just return */ + self->priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); + + /* Chain up parent's gathering enable */ + iface_modem_location_parent->enable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); +} + +/*****************************************************************************/ +/* Check support (Time interface) */ + +static gboolean +modem_time_check_support_finish (MMIfaceModemTime *_self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +modem_time_check_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + + /* Responses are checked in the sequence parser, ignore overall result */ + mm_base_modem_at_sequence_finish (_self, res, NULL, NULL); + + g_task_return_boolean (task, + (self->priv->nwtime_support == FEATURE_SUPPORTED || + self->priv->time_support == FEATURE_SUPPORTED)); + g_object_unref (task); +} + +static MMBaseModemAtResponseProcessorResult +modem_check_time_reply (MMBaseModem *_self, + gpointer none, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + + if (!error) { + if (strstr (response, "^NTCT")) + self->priv->nwtime_support = FEATURE_SUPPORTED; + else if (strstr (response, "^TIME")) + self->priv->time_support = FEATURE_SUPPORTED; + } else { + if (strstr (command, "^NTCT")) + self->priv->nwtime_support = FEATURE_NOT_SUPPORTED; + else if (strstr (command, "^TIME")) + self->priv->time_support = FEATURE_NOT_SUPPORTED; + } + + *result = NULL; + *result_error = NULL; + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; +} + +static const MMBaseModemAtCommand time_cmd_sequence[] = { + { "^NTCT?", 3, FALSE, modem_check_time_reply }, /* 3GPP/LTE */ + { "^TIME", 3, FALSE, modem_check_time_reply }, /* CDMA */ + { NULL } +}; + +static void +modem_time_check_support (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_sequence (MM_BASE_MODEM (self), + time_cmd_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + (GAsyncReadyCallback)modem_time_check_ready, + task); +} + +/*****************************************************************************/ +/* Check support (Signal interface) */ + +static void +hcsq_check_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (response) + g_task_return_boolean (task, TRUE); + else + g_task_return_error (task, error); + + g_object_unref (task); +} + +static gboolean +signal_check_support_finish (MMIfaceModemSignal *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +signal_check_support (MMIfaceModemSignal *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^HCSQ?", + 3, + FALSE, + (GAsyncReadyCallback)hcsq_check_ready, + task); +} + +/*****************************************************************************/ +/* Load extended signal information */ + +static void +detailed_signal_free (DetailedSignal *signal) +{ + detailed_signal_clear (signal); + g_slice_free (DetailedSignal, signal); +} + +static gboolean +signal_load_values_finish (MMIfaceModemSignal *self, + GAsyncResult *res, + MMSignal **cdma, + MMSignal **evdo, + MMSignal **gsm, + MMSignal **umts, + MMSignal **lte, + MMSignal **nr5g, + GError **error) +{ + DetailedSignal *signals; + + signals = g_task_propagate_pointer (G_TASK (res), error); + if (!signals) + return FALSE; + + *cdma = signals->cdma ? g_object_ref (signals->cdma) : NULL; + *evdo = signals->evdo ? g_object_ref (signals->evdo) : NULL; + *gsm = signals->gsm ? g_object_ref (signals->gsm) : NULL; + *umts = signals->umts ? g_object_ref (signals->umts) : NULL; + *lte = signals->lte ? g_object_ref (signals->lte) : NULL; + *nr5g = signals->nr5g ? g_object_ref (signals->nr5g) : NULL; + + detailed_signal_free (signals); + return TRUE; +} + +static void +hcsq_get_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + DetailedSignal *signals; + GError *error = NULL; + + /* Don't care about the response; it will have been parsed by the HCSQ + * unsolicited event handler and self->priv->detailed_signal will already + * be updated. + */ + if (!mm_base_modem_at_command_finish (_self, res, &error)) { + mm_obj_dbg (self, "^HCSQ failed: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + signals = g_slice_new0 (DetailedSignal); + signals->cdma = self->priv->detailed_signal.cdma ? g_object_ref (self->priv->detailed_signal.cdma) : NULL; + signals->evdo = self->priv->detailed_signal.evdo ? g_object_ref (self->priv->detailed_signal.evdo) : NULL; + signals->gsm = self->priv->detailed_signal.gsm ? g_object_ref (self->priv->detailed_signal.gsm) : NULL; + signals->umts = self->priv->detailed_signal.umts ? g_object_ref (self->priv->detailed_signal.umts) : NULL; + signals->lte = self->priv->detailed_signal.lte ? g_object_ref (self->priv->detailed_signal.lte) : NULL; + + g_task_return_pointer (task, signals, (GDestroyNotify)detailed_signal_free); + g_object_unref (task); +} + +static void +signal_load_values (MMIfaceModemSignal *_self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GTask *task; + + task = g_task_new (self, cancellable, callback, user_data); + + /* Clear any previous detailed signal values to get new ones */ + detailed_signal_clear (&self->priv->detailed_signal); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^HCSQ?", + 3, + FALSE, + (GAsyncReadyCallback)hcsq_get_ready, + task); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +set_ignored_unsolicited_events_handlers (MMBroadbandModemHuawei *self) +{ + GList *ports, *l; + + ports = mm_broadband_modem_huawei_get_at_port_list (self); + + /* Enable/disable unsolicited events in given port */ + for (l = ports; l; l = g_list_next (l)) { + MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data); + + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->boot_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->connect_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->csnr_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->cusatp_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->cusatend_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->dsdormant_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->simst_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->srvst_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->stin_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->pdpdeact_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->ndisend_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->rfswitch_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->position_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->posend_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->ecclist_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->ltersrp_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->cschannelinfo_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->ccallstate_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->eons_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + self->priv->lwurc_regex, + NULL, NULL, NULL); + } + + g_list_free_full (ports, g_object_unref); +} + +static void +gps_trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +static void +setup_ports (MMBroadbandModem *self) +{ + MMPortSerialGps *gps_data_port; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_huawei_parent_class)->setup_ports (self); + + /* Unsolicited messages to always ignore */ + set_ignored_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self)); + + /* Now reset the unsolicited messages we'll handle when enabled */ + set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE); + set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE); + + /* NMEA GPS monitoring */ + gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_data_port) { + /* make sure GPS is stopped incase it was left enabled */ + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + "^WPEND", + 3, FALSE, FALSE, NULL, NULL, NULL); + /* Add handler for the NMEA traces */ + mm_port_serial_gps_add_trace_handler (gps_data_port, + (MMPortSerialGpsTraceFn)gps_trace_received, + self, NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemHuawei * +mm_broadband_modem_huawei_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_HUAWEI, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer (TTY) or Huawei bearer (NET) supported */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_huawei_init (MMBroadbandModemHuawei *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_HUAWEI, + MMBroadbandModemHuaweiPrivate); + /* Prepare regular expressions to setup */ + self->priv->rssi_regex = g_regex_new ("\\r\\n\\^RSSI:\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->rssilvl_regex = g_regex_new ("\\r\\n\\^RSSILVL:\\s*(\\d+)\\r+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->hrssilvl_regex = g_regex_new ("\\r\\n\\^HRSSILVL:\\s*(\\d+)\\r+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + /* 3GPP: <cr><lf>^MODE:5<cr><lf> + * CDMA: <cr><lf>^MODE: 2<cr><cr><lf> + */ + self->priv->mode_regex = g_regex_new ("\\r\\n\\^MODE:\\s*(\\d*),?(\\d*)\\r+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->dsflowrpt_regex = g_regex_new ("\\r\\n\\^DSFLOWRPT:(.+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ndisstat_regex = g_regex_new ("\\r\\n(\\^NDISSTAT:.+)\\r+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + self->priv->orig_regex = g_regex_new ("\\r\\n\\^ORIG:\\s*(\\d+),\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->conf_regex = g_regex_new ("\\r\\n\\^CONF:\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->conn_regex = g_regex_new ("\\r\\n\\^CONN:\\s*(\\d+),\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->cend_regex = g_regex_new ("\\r\\n\\^CEND:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*(\\d*))?\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ddtmf_regex = g_regex_new ("\\r\\n\\^DDTMF:\\s*([0-9A-D\\*\\#])\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + self->priv->boot_regex = g_regex_new ("\\r\\n\\^BOOT:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->connect_regex = g_regex_new ("\\r\\n\\^CONNECT .+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->csnr_regex = g_regex_new ("\\r\\n\\^CSNR:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->cusatp_regex = g_regex_new ("\\r\\n\\+CUSATP:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->cusatend_regex = g_regex_new ("\\r\\n\\+CUSATEND\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->dsdormant_regex = g_regex_new ("\\r\\n\\^DSDORMANT:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->simst_regex = g_regex_new ("\\r\\n\\^SIMST:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->srvst_regex = g_regex_new ("\\r\\n\\^SRVST:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->stin_regex = g_regex_new ("\\r\\n\\^STIN:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->hcsq_regex = g_regex_new ("\\r\\n(\\^HCSQ:.+)\\r+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->pdpdeact_regex = g_regex_new ("\\r\\n\\^PDPDEACT:.+\\r+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ndisend_regex = g_regex_new ("\\r\\n\\^NDISEND:.+\\r+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->rfswitch_regex = g_regex_new ("\\r\\n\\^RFSWITCH:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->position_regex = g_regex_new ("\\r\\n\\^POSITION:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->posend_regex = g_regex_new ("\\r\\n\\^POSEND:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ecclist_regex = g_regex_new ("\\r\\n\\^ECCLIST:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ltersrp_regex = g_regex_new ("\\r\\n\\^LTERSRP:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->cschannelinfo_regex = g_regex_new ("\\r\\n\\^CSCHANNELINFO:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ccallstate_regex = g_regex_new ("\\r\\n\\^CCALLSTATE:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->eons_regex = g_regex_new ("\\r\\n\\^EONS:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->lwurc_regex = g_regex_new ("\\r\\n\\^LWURC:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + self->priv->ndisdup_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->rfswitch_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->sysinfoex_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->syscfg_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->syscfgex_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->prefmode_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->nwtime_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->time_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->cvoice_support = FEATURE_SUPPORT_UNKNOWN; +} + +static void +dispose (GObject *object) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (object); + + detailed_signal_clear (&self->priv->detailed_signal); + + G_OBJECT_CLASS (mm_broadband_modem_huawei_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (object); + + g_regex_unref (self->priv->rssi_regex); + g_regex_unref (self->priv->rssilvl_regex); + g_regex_unref (self->priv->hrssilvl_regex); + g_regex_unref (self->priv->mode_regex); + g_regex_unref (self->priv->dsflowrpt_regex); + g_regex_unref (self->priv->ndisstat_regex); + g_regex_unref (self->priv->orig_regex); + g_regex_unref (self->priv->conf_regex); + g_regex_unref (self->priv->conn_regex); + g_regex_unref (self->priv->cend_regex); + g_regex_unref (self->priv->ddtmf_regex); + + g_regex_unref (self->priv->boot_regex); + g_regex_unref (self->priv->connect_regex); + g_regex_unref (self->priv->csnr_regex); + g_regex_unref (self->priv->cusatp_regex); + g_regex_unref (self->priv->cusatend_regex); + g_regex_unref (self->priv->dsdormant_regex); + g_regex_unref (self->priv->simst_regex); + g_regex_unref (self->priv->srvst_regex); + g_regex_unref (self->priv->stin_regex); + g_regex_unref (self->priv->hcsq_regex); + g_regex_unref (self->priv->pdpdeact_regex); + g_regex_unref (self->priv->ndisend_regex); + g_regex_unref (self->priv->rfswitch_regex); + g_regex_unref (self->priv->position_regex); + g_regex_unref (self->priv->posend_regex); + g_regex_unref (self->priv->ecclist_regex); + g_regex_unref (self->priv->ltersrp_regex); + g_regex_unref (self->priv->cschannelinfo_regex); + g_regex_unref (self->priv->ccallstate_regex); + g_regex_unref (self->priv->eons_regex); + g_regex_unref (self->priv->lwurc_regex); + + if (self->priv->syscfg_supported_modes) + g_array_unref (self->priv->syscfg_supported_modes); + if (self->priv->syscfgex_supported_modes) + g_array_unref (self->priv->syscfgex_supported_modes); + if (self->priv->prefmode_supported_modes) + g_array_unref (self->priv->prefmode_supported_modes); + + G_OBJECT_CLASS (mm_broadband_modem_huawei_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->reset = reset; + iface->reset_finish = reset_finish; + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; + iface->modem_after_sim_unlock = modem_after_sim_unlock; + iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish; + iface->load_current_bands = load_current_bands; + iface->load_current_bands_finish = load_current_bands_finish; + iface->set_current_bands = set_current_bands; + iface->set_current_bands_finish = set_current_bands_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->load_signal_quality = modem_load_signal_quality; + iface->load_signal_quality_finish = modem_load_signal_quality_finish; + iface->create_bearer = huawei_modem_create_bearer; + iface->create_bearer_finish = huawei_modem_create_bearer_finish; + iface->load_power_state = load_power_state; + iface->load_power_state_finish = load_power_state_finish; + iface->modem_power_up = huawei_modem_power_up; + iface->modem_power_up_finish = huawei_modem_power_up_finish; + iface->modem_power_down = huawei_modem_power_down; + iface->modem_power_down_finish = huawei_modem_power_down_finish; + iface->create_sim = huawei_modem_create_sim; + iface->create_sim_finish = huawei_modem_create_sim_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish; +} + +static void +iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface) +{ + iface->encode = encode; + iface->decode = decode; +} + +static void +iface_modem_cdma_init (MMIfaceModemCdma *iface) +{ + iface_modem_cdma_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = modem_cdma_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_cdma_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish; + iface->setup_registration_checks = setup_registration_checks; + iface->setup_registration_checks_finish = setup_registration_checks_finish; + iface->get_detailed_registration_state = get_detailed_registration_state; + iface->get_detailed_registration_state_finish = get_detailed_registration_state_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = location_load_capabilities; + iface->load_capabilities_finish = location_load_capabilities_finish; + iface->enable_location_gathering = enable_location_gathering; + iface->enable_location_gathering_finish = enable_location_gathering_finish; + iface->disable_location_gathering = disable_location_gathering; + iface->disable_location_gathering_finish = disable_location_gathering_finish; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface->check_support = modem_time_check_support; + iface->check_support_finish = modem_time_check_support_finish; + iface->load_network_time = modem_time_load_network_time_or_zone; + iface->load_network_time_finish = modem_time_load_network_time_finish; + iface->load_network_timezone = modem_time_load_network_time_or_zone; + iface->load_network_timezone_finish = modem_time_load_network_timezone_finish; +} + +static void +iface_modem_voice_init (MMIfaceModemVoice *iface) +{ + iface_modem_voice_parent = g_type_interface_peek_parent (iface); + + iface->check_support = modem_voice_check_support; + iface->check_support_finish = modem_voice_check_support_finish; + iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_voice_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_voice_cleanup_unsolicited_events_finish; + iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_voice_disable_unsolicited_events_finish; + iface->setup_in_call_audio_channel = modem_voice_setup_in_call_audio_channel; + iface->setup_in_call_audio_channel_finish = modem_voice_setup_in_call_audio_channel_finish; + iface->cleanup_in_call_audio_channel = modem_voice_cleanup_in_call_audio_channel; + iface->cleanup_in_call_audio_channel_finish = modem_voice_cleanup_in_call_audio_channel_finish; + + iface->create_call = create_call; +} + +static void +iface_modem_signal_init (MMIfaceModemSignal *iface) +{ + iface->check_support = signal_check_support; + iface->check_support_finish = signal_check_support_finish; + iface->load_values = signal_load_values; + iface->load_values_finish = signal_load_values_finish; +} + +static void +mm_broadband_modem_huawei_class_init (MMBroadbandModemHuaweiClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemHuaweiPrivate)); + + object_class->dispose = dispose; + object_class->finalize = finalize; + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/huawei/mm-broadband-modem-huawei.h b/src/plugins/huawei/mm-broadband-modem-huawei.h new file mode 100644 index 00000000..9fb16811 --- /dev/null +++ b/src/plugins/huawei/mm-broadband-modem-huawei.h @@ -0,0 +1,55 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_HUAWEI_H +#define MM_BROADBAND_MODEM_HUAWEI_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_HUAWEI (mm_broadband_modem_huawei_get_type ()) +#define MM_BROADBAND_MODEM_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_HUAWEI, MMBroadbandModemHuawei)) +#define MM_BROADBAND_MODEM_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_HUAWEI, MMBroadbandModemHuaweiClass)) +#define MM_IS_BROADBAND_MODEM_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_HUAWEI)) +#define MM_IS_BROADBAND_MODEM_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_HUAWEI)) +#define MM_BROADBAND_MODEM_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_HUAWEI, MMBroadbandModemHuaweiClass)) + +typedef struct _MMBroadbandModemHuawei MMBroadbandModemHuawei; +typedef struct _MMBroadbandModemHuaweiClass MMBroadbandModemHuaweiClass; +typedef struct _MMBroadbandModemHuaweiPrivate MMBroadbandModemHuaweiPrivate; + +struct _MMBroadbandModemHuawei { + MMBroadbandModem parent; + MMBroadbandModemHuaweiPrivate *priv; +}; + +struct _MMBroadbandModemHuaweiClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_huawei_get_type (void); + +MMBroadbandModemHuawei *mm_broadband_modem_huawei_new (const gchar *device, + const gchar **driver, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +MMPortSerialAt *mm_broadband_modem_huawei_peek_port_at_for_data (MMBroadbandModemHuawei *self, + MMPort *port); +GList *mm_broadband_modem_huawei_get_at_port_list (MMBroadbandModemHuawei *self); + +#endif /* MM_BROADBAND_MODEM_HUAWEI_H */ diff --git a/src/plugins/huawei/mm-modem-helpers-huawei.c b/src/plugins/huawei/mm-modem-helpers-huawei.c new file mode 100644 index 00000000..67bb7089 --- /dev/null +++ b/src/plugins/huawei/mm-modem-helpers-huawei.c @@ -0,0 +1,1546 @@ +/* -*- 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) 2013 Huawei Technologies Co., Ltd + * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-common-helpers.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-huawei.h" +#include "mm-huawei-enums-types.h" + +/*****************************************************************************/ +/* ^NDISSTAT / ^NDISSTATQRY response parser */ + +gboolean +mm_huawei_parse_ndisstatqry_response (const gchar *response, + gboolean *ipv4_available, + gboolean *ipv4_connected, + gboolean *ipv6_available, + gboolean *ipv6_connected, + GError **error) +{ + GError *inner_error = NULL; + + if (!response || + !(g_ascii_strncasecmp (response, "^NDISSTAT:", strlen ("^NDISSTAT:")) == 0 || + g_ascii_strncasecmp (response, "^NDISSTATQRY:", strlen ("^NDISSTATQRY:")) == 0)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing ^NDISSTAT / ^NDISSTATQRY prefix"); + return FALSE; + } + + *ipv4_available = FALSE; + *ipv6_available = FALSE; + + /* The response maybe as: + * ^NDISSTAT: 1,,,IPV4 + * ^NDISSTAT: 0,33,,IPV6 + * ^NDISSTATQRY: 1,,,IPV4 + * ^NDISSTATQRY: 0,33,,IPV6 + * OK + * + * Or, in newer firmwares: + * ^NDISSTATQRY:0,,,"IPV4",0,,,"IPV6" + * OK + * + * Or, even (handled separately): + * ^NDISSTATQry:1 + * OK + */ + + /* If multiple fields available, try first parsing method */ + if (strchr (response, ',')) { + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + r = g_regex_new ("\\^NDISSTAT(?:QRY)?(?:Qry)?:\\s*(\\d),([^,]*),([^,]*),([^,\\r\\n]*)(?:\\r\\n)?" + "(?:\\^NDISSTAT:|\\^NDISSTATQRY:)?\\s*,?(\\d)?,?([^,]*)?,?([^,]*)?,?([^,\\r\\n]*)?(?:\\r\\n)?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, + 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + guint ip_type_field = 4; + + /* IPv4 and IPv6 are fields 4 and (if available) 8 */ + + while (!inner_error && ip_type_field <= 8) { + gchar *ip_type_str; + guint connected; + + ip_type_str = mm_get_string_unquoted_from_match_info (match_info, ip_type_field); + if (!ip_type_str) + break; + + if (!mm_get_uint_from_match_info (match_info, (ip_type_field - 3), &connected) || + (connected != 0 && connected != 1)) { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse ^NDISSTAT / ^NDISSTATQRY fields"); + } else if (g_ascii_strcasecmp (ip_type_str, "IPV4") == 0) { + *ipv4_available = TRUE; + *ipv4_connected = (gboolean)connected; + } else if (g_ascii_strcasecmp (ip_type_str, "IPV6") == 0) { + *ipv6_available = TRUE; + *ipv6_connected = (gboolean)connected; + } + + g_free (ip_type_str); + ip_type_field += 4; + } + } + } + /* No separate IPv4/IPv6 info given just connected/not connected */ + else { + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + r = g_regex_new ("\\^NDISSTAT(?:QRY)?(?:Qry)?:\\s*(\\d)(?:\\r\\n)?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, + 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + guint connected; + + if (!mm_get_uint_from_match_info (match_info, 1, &connected) || + (connected != 0 && connected != 1)) { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse ^NDISSTAT / ^NDISSTATQRY fields"); + } else { + /* We'll assume IPv4 */ + *ipv4_available = TRUE; + *ipv4_connected = (gboolean)connected; + } + } + } + + if (!ipv4_available && !ipv6_available) { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't find IPv4 or IPv6 info in ^NDISSTAT / ^NDISSTATQRY response"); + } + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ +/* ^DHCP response parser */ + +static gboolean +match_info_to_ip4_addr (GMatchInfo *match_info, + guint match_index, + guint *out_addr) +{ + g_autofree gchar *s = NULL; + g_autofree guint8 *bin = NULL; + gchar buf[9]; + gsize len; + gsize bin_len; + guint32 aux; + + s = g_match_info_fetch (match_info, match_index); + g_return_val_if_fail (s != NULL, FALSE); + + len = strlen (s); + if (len == 1 && s[0] == '0') { + *out_addr = 0; + return TRUE; + } + + if (len < 7 || len > 8) + return FALSE; + + /* Handle possibly missing leading zero */ + memset (buf, 0, sizeof (buf)); + if (len == 7) { + strcpy (&buf[1], s); + buf[0] = '0'; + } else if (len == 8) + strcpy (buf, s); + else + g_assert_not_reached (); + + bin = mm_utils_hexstr2bin (buf, -1, &bin_len, NULL); + if (!bin || bin_len != 4) + return FALSE; + + memcpy (&aux, bin, 4); + *out_addr = GUINT32_SWAP_LE_BE (aux); + return TRUE; +} + +gboolean +mm_huawei_parse_dhcp_response (const char *reply, + guint *out_address, + guint *out_prefix, + guint *out_gateway, + guint *out_dns1, + guint *out_dns2, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + gboolean matched; + GError *match_error = NULL; + + g_assert (reply != NULL); + g_assert (out_address != NULL); + g_assert (out_prefix != NULL); + g_assert (out_gateway != NULL); + g_assert (out_dns1 != NULL); + g_assert (out_dns2 != NULL); + + /* Format: + * + * ^DHCP: <address>,<netmask>,<gateway>,<?>,<dns1>,<dns2>,<uplink>,<downlink> + * + * All numbers are hexadecimal representations of IPv4 addresses, with + * least-significant byte first. eg, 192.168.50.32 is expressed as + * "2032A8C0". Sometimes leading zeros are stripped, so "1010A0A" is + * actually 10.10.1.1. + */ + + r = g_regex_new ("\\^DHCP:\\s*(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),.*$", 0, 0, NULL); + g_assert (r != NULL); + + matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error); + if (!matched) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse ^DHCP results: "); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match ^DHCP reply"); + } + } else { + guint netmask; + + if (match_info_to_ip4_addr (match_info, 1, out_address) && + match_info_to_ip4_addr (match_info, 2, &netmask) && + match_info_to_ip4_addr (match_info, 3, out_gateway) && + match_info_to_ip4_addr (match_info, 5, out_dns1) && + match_info_to_ip4_addr (match_info, 6, out_dns2)) { + *out_prefix = mm_count_bits_set (netmask); + matched = TRUE; + } + } + + return matched; +} + +/*****************************************************************************/ +/* ^SYSINFO response parser */ + +gboolean +mm_huawei_parse_sysinfo_response (const char *reply, + guint *out_srv_status, + guint *out_srv_domain, + guint *out_roam_status, + guint *out_sys_mode, + guint *out_sim_state, + gboolean *out_sys_submode_valid, + guint *out_sys_submode, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + gboolean matched; + GError *match_error = NULL; + + g_assert (out_srv_status != NULL); + g_assert (out_srv_domain != NULL); + g_assert (out_roam_status != NULL); + g_assert (out_sys_mode != NULL); + g_assert (out_sim_state != NULL); + g_assert (out_sys_submode_valid != NULL); + g_assert (out_sys_submode != NULL); + + /* Format: + * + * ^SYSINFO: <srv_status>,<srv_domain>,<roam_status>,<sys_mode>,<sim_state>[,<reserved>,<sys_submode>] + */ + + /* Can't just use \d here since sometimes you get "^SYSINFO:2,1,0,3,1,,3" */ + r = g_regex_new ("\\^SYSINFO:\\s*(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),?(\\d+)?,?(\\d+)?$", 0, 0, NULL); + g_assert (r != NULL); + + matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error); + if (!matched) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse ^SYSINFO results: "); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match ^SYSINFO reply"); + } + } else { + mm_get_uint_from_match_info (match_info, 1, out_srv_status); + mm_get_uint_from_match_info (match_info, 2, out_srv_domain); + mm_get_uint_from_match_info (match_info, 3, out_roam_status); + mm_get_uint_from_match_info (match_info, 4, out_sys_mode); + mm_get_uint_from_match_info (match_info, 5, out_sim_state); + + /* Remember that g_match_info_get_match_count() includes match #0 */ + if (g_match_info_get_match_count (match_info) >= 8) { + *out_sys_submode_valid = TRUE; + mm_get_uint_from_match_info (match_info, 7, out_sys_submode); + } + } + + return matched; +} + +/*****************************************************************************/ +/* ^SYSINFOEX response parser */ + +gboolean +mm_huawei_parse_sysinfoex_response (const char *reply, + guint *out_srv_status, + guint *out_srv_domain, + guint *out_roam_status, + guint *out_sim_state, + guint *out_sys_mode, + guint *out_sys_submode, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + gboolean matched; + GError *match_error = NULL; + + g_assert (out_srv_status != NULL); + g_assert (out_srv_domain != NULL); + g_assert (out_roam_status != NULL); + g_assert (out_sim_state != NULL); + g_assert (out_sys_mode != NULL); + g_assert (out_sys_submode != NULL); + + /* Format: + * + * ^SYSINFOEX: <srv_status>,<srv_domain>,<roam_status>,<sim_state>,<reserved>,<sysmode>,<sysmode_name>,<submode>,<submode_name> + * + * <sysmode_name> and <submode_name> may not be quoted on some Huawei modems (e.g. E303). + */ + + /* ^SYSINFOEX:2,3,0,1,,3,"WCDMA",41,"HSPA+" */ + + r = g_regex_new ("\\^SYSINFOEX:\\s*(\\d+),(\\d+),(\\d+),(\\d+),?(\\d*),(\\d+),\"?([^\"]*)\"?,(\\d+),\"?([^\"]*)\"?$", 0, 0, NULL); + g_assert (r != NULL); + + matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error); + if (!matched) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse ^SYSINFOEX results: "); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match ^SYSINFOEX reply"); + } + } else { + mm_get_uint_from_match_info (match_info, 1, out_srv_status); + mm_get_uint_from_match_info (match_info, 2, out_srv_domain); + mm_get_uint_from_match_info (match_info, 3, out_roam_status); + mm_get_uint_from_match_info (match_info, 4, out_sim_state); + + /* We just ignore the sysmode and submode name strings */ + mm_get_uint_from_match_info (match_info, 6, out_sys_mode); + mm_get_uint_from_match_info (match_info, 8, out_sys_submode); + } + + return matched; +} + +/*****************************************************************************/ +/* ^PREFMODE test parser + * + * AT^PREFMODE=? + * ^PREFMODE:(2,4,8) + */ + +static gboolean +mode_from_prefmode (guint huawei_mode, + MMModemMode *modem_mode, + GError **error) +{ + g_assert (modem_mode != NULL); + + *modem_mode = MM_MODEM_MODE_NONE; + switch (huawei_mode) { + case 2: + *modem_mode = MM_MODEM_MODE_2G; + break; + case 4: + *modem_mode = MM_MODEM_MODE_3G; + break; + case 8: + *modem_mode = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + break; + default: + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "No translation from huawei prefmode '%u' to mode", + huawei_mode); + } + + return *modem_mode != MM_MODEM_MODE_NONE ? TRUE : FALSE; +} + +GArray * +mm_huawei_parse_prefmode_test (const gchar *response, + gpointer log_object, + GError **error) +{ + gchar **split; + guint i; + MMModemMode all = MM_MODEM_MODE_NONE; + GArray *out; + + response = mm_strip_tag (response, "^PREFMODE:"); + split = g_strsplit_set (response, " (,)\r\n", -1); + if (!split) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unexpected ^PREFMODE format output"); + return NULL; + } + + out = g_array_sized_new (FALSE, + FALSE, + sizeof (MMHuaweiPrefmodeCombination), + 3); + for (i = 0; split[i]; i++) { + guint val; + MMModemMode preferred = MM_MODEM_MODE_NONE; + GError *inner_error = NULL; + MMHuaweiPrefmodeCombination combination; + + if (split[i][0] == '\0') + continue; + + if (!mm_get_uint_from_str (split[i], &val)) { + mm_obj_dbg (log_object, "error parsing ^PREFMODE value '%s'", split[i]); + continue; + } + + if (!mode_from_prefmode (val, &preferred, &inner_error)) { + mm_obj_dbg (log_object, "unhandled ^PREFMODE value: %s", inner_error->message); + g_error_free (inner_error); + continue; + } + + combination.prefmode = val; + combination.allowed = MM_MODEM_MODE_NONE; /* reset it later */ + combination.preferred = preferred; + + all |= preferred; + + g_array_append_val (out, combination); + } + g_strfreev (split); + + /* No value */ + if (out->len == 0) { + g_array_unref (out); + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "^PREFMODE response contains no valid values"); + return NULL; + } + + /* Single value listed; PREFERRED=NONE... */ + if (out->len == 1) { + MMHuaweiPrefmodeCombination *combination; + + combination = &g_array_index (out, MMHuaweiPrefmodeCombination, 0); + combination->allowed = all; + combination->preferred = MM_MODEM_MODE_NONE; + } else { + /* Multiple values, reset ALLOWED */ + for (i = 0; i < out->len; i++) { + MMHuaweiPrefmodeCombination *combination; + + combination = &g_array_index (out, MMHuaweiPrefmodeCombination, i); + combination->allowed = all; + if (combination->preferred == all) + combination->preferred = MM_MODEM_MODE_NONE; + } + } + + return out; +} + +/*****************************************************************************/ +/* ^PREFMODE response parser */ + +const MMHuaweiPrefmodeCombination * +mm_huawei_parse_prefmode_response (const gchar *response, + const GArray *supported_mode_combinations, + GError **error) +{ + guint mode; + guint i; + + /* Format: + * + * ^PREFMODE: <mode> + */ + + response = mm_strip_tag (response, "^PREFMODE:"); + if (!mm_get_uint_from_str (response, &mode)) { + /* Dump error to upper layer */ + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unexpected PREFMODE response: '%s'", + response); + return NULL; + } + + /* Look for current modes among the supported ones */ + for (i = 0; i < supported_mode_combinations->len; i++) { + const MMHuaweiPrefmodeCombination *combination; + + combination = &g_array_index (supported_mode_combinations, + MMHuaweiPrefmodeCombination, + i); + if (mode == combination->prefmode) + return combination; + } + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "No PREFMODE combination found matching the current one (%d)", + mode); + return NULL; +} + +/*****************************************************************************/ +/* ^SYSCFG test parser */ + +static gchar ** +split_groups (const gchar *str, + GError **error) +{ + const gchar *p = str; + GPtrArray *out; + guint groups = 0; + + /* + * Split string: (a),((b1),(b2)),,(d),((e1),(e2)) + * Into: + * - a + * - (b1),(b2) + * - + * - d + * - (e1),(e2) + */ + + out = g_ptr_array_new_with_free_func (g_free); + + while (TRUE) { + const gchar *start; + guint inner_groups; + + /* Skip whitespaces */ + while (*p == ' ' || *p == '\r' || *p == '\n') + p++; + + /* We're done, return */ + if (*p == '\0') { + g_ptr_array_set_size (out, out->len + 1); + return (gchar **) g_ptr_array_free (out, FALSE); + } + + /* Group separators */ + if (groups > 0) { + if (*p != ',') { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unexpected group separator"); + g_ptr_array_unref (out); + return NULL; + } + p++; + } + + /* Skip whitespaces */ + while (*p == ' ' || *p == '\r' || *p == '\n') + p++; + + /* New group */ + groups++; + + /* Empty group? */ + if (*p == ',' || *p == '\0') { + g_ptr_array_add (out, g_strdup ("")); + continue; + } + + /* No group start? */ + if (*p != '(') { + /* Error */ + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Expected '(' not found"); + g_ptr_array_unref (out); + return NULL; + } + p++; + + inner_groups = 0; + start = p; + while (TRUE) { + if (*p == '(') { + inner_groups++; + p++; + continue; + } + + if (*p == '\0') { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Early end of string found, unfinished group"); + g_ptr_array_unref (out); + return NULL; + } + + if (*p == ')') { + gchar *group; + + if (inner_groups > 0) { + inner_groups--; + p++; + continue; + } + + group = g_strndup (start, p - start); + g_ptr_array_add (out, group); + p++; + break; + } + + /* keep on */ + p++; + } + } + + g_assert_not_reached (); +} + +static gboolean +mode_from_syscfg (guint huawei_mode, + MMModemMode *modem_mode, + GError **error) +{ + g_assert (modem_mode != NULL); + + *modem_mode = MM_MODEM_MODE_NONE; + switch (huawei_mode) { + case 2: + *modem_mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G; + break; + case 13: + *modem_mode = MM_MODEM_MODE_2G; + break; + case 14: + *modem_mode = MM_MODEM_MODE_3G; + break; + case 16: + /* ignore */ + break; + default: + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "No translation from huawei prefmode '%u' to mode", + huawei_mode); + } + + return *modem_mode != MM_MODEM_MODE_NONE ? TRUE : FALSE; +} + +static GArray * +parse_syscfg_modes (const gchar *modes_str, + const gchar *acqorder_str, + gpointer log_object, + GError **error) +{ + GArray *out; + gchar **split; + guint i; + gint min_acqorder = 0; + gint max_acqorder = 0; + + /* Start parsing acquisition order */ + if (!sscanf (acqorder_str, "%d-%d", &min_acqorder, &max_acqorder)) + mm_obj_dbg (log_object, "error parsing ^SYSCFG acquisition order range '%s'", acqorder_str); + + /* Just in case, we default to supporting only auto */ + if (max_acqorder < min_acqorder) { + min_acqorder = 0; + max_acqorder = 0; + } + + /* Now parse modes */ + split = g_strsplit (modes_str, ",", -1); + out = g_array_sized_new (FALSE, + FALSE, + sizeof (MMHuaweiSyscfgCombination), + g_strv_length (split)); + for (i = 0; split[i]; i++) { + guint val; + guint allowed = MM_MODEM_MODE_NONE; + GError *inner_error = NULL; + MMHuaweiSyscfgCombination combination; + + if (!mm_get_uint_from_str (mm_strip_quotes (split[i]), &val)) { + mm_obj_dbg (log_object, "error parsing ^SYSCFG mode value: %s", split[i]); + continue; + } + + if (!mode_from_syscfg (val, &allowed, &inner_error)) { + if (inner_error) { + mm_obj_dbg (log_object, "unhandled ^SYSCFG: %s", inner_error->message); + g_error_free (inner_error); + } + continue; + } + + switch (allowed) { + case MM_MODEM_MODE_2G: + case MM_MODEM_MODE_3G: + /* single mode */ + combination.allowed = allowed; + combination.preferred = MM_MODEM_MODE_NONE; + combination.mode = val; + combination.acqorder = 0; + g_array_append_val (out, combination); + break; + case (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G): + /* 2G and 3G; auto */ + combination.allowed = allowed; + combination.mode = val; + if (min_acqorder == 0) { + combination.preferred = MM_MODEM_MODE_NONE; + combination.acqorder = 0; + g_array_append_val (out, combination); + } + /* 2G and 3G; 2G preferred */ + if (min_acqorder <= 1 && max_acqorder >= 1) { + combination.preferred = MM_MODEM_MODE_2G; + combination.acqorder = 1; + g_array_append_val (out, combination); + } + /* 2G and 3G; 3G preferred */ + if (min_acqorder <= 2 && max_acqorder >= 2) { + combination.preferred = MM_MODEM_MODE_3G; + combination.acqorder = 2; + g_array_append_val (out, combination); + } + break; + default: + g_assert_not_reached (); + } + } + + g_strfreev (split); + + /* If we didn't build a valid array of combinations, return an error */ + if (out->len == 0) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot parse list of allowed mode combinations: '%s,%s'", + modes_str, + acqorder_str); + g_array_unref (out); + return NULL; + } + + return out; +} + +GArray * +mm_huawei_parse_syscfg_test (const gchar *response, + gpointer log_object, + GError **error) +{ + gchar **split; + GError *inner_error = NULL; + GArray *out; + + if (!response || !g_str_has_prefix (response, "^SYSCFG:")) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Missing ^SYSCFG prefix"); + return NULL; + } + + /* Examples: + * + * ^SYSCFG:(2,13,14,16), + * (0-3), + * ((400000,"WCDMA2100")), + * (0-2), + * (0-4) + */ + split = split_groups (mm_strip_tag (response, "^SYSCFG:"), error); + if (!split) + return NULL; + + /* We expect 5 string chunks */ + if (g_strv_length (split) < 5) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unexpected ^SYSCFG format"); + g_strfreev (split); + return FALSE; + } + + /* Parse supported mode combinations */ + out = parse_syscfg_modes (split[0], split[1], log_object, &inner_error); + + g_strfreev (split); + + if (inner_error) { + g_propagate_error (error, inner_error); + return NULL; + } + + return out; +} + +/*****************************************************************************/ +/* ^SYSCFG response parser */ + +const MMHuaweiSyscfgCombination * +mm_huawei_parse_syscfg_response (const gchar *response, + const GArray *supported_mode_combinations, + GError **error) +{ + gchar **split; + guint mode; + guint acqorder; + guint i; + + if (!response || !g_str_has_prefix (response, "^SYSCFG:")) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Missing ^SYSCFG prefix"); + return NULL; + } + + /* Format: + * + * ^SYSCFG: <mode>,<acqorder>,<band>,<roam>,<srvdomain> + */ + + response = mm_strip_tag (response, "^SYSCFG:"); + split = g_strsplit (response, ",", -1); + + /* We expect 5 string chunks */ + if (g_strv_length (split) < 5 || + !mm_get_uint_from_str (split[0], &mode) || + !mm_get_uint_from_str (split[1], &acqorder)) { + /* Dump error to upper layer */ + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unexpected ^SYSCFG response: '%s'", + response); + g_strfreev (split); + return NULL; + } + + /* Fix invalid modes with non-sensical acquisition orders */ + if (mode == 14 && acqorder != 0) /* WCDMA only but acqorder != "Automatic" */ + acqorder = 0; + else if (mode == 13 && acqorder != 0) /* GSM only but acqorder != "Automatic" */ + acqorder = 0; + + /* Look for current modes among the supported ones */ + for (i = 0; i < supported_mode_combinations->len; i++) { + const MMHuaweiSyscfgCombination *combination; + + combination = &g_array_index (supported_mode_combinations, + MMHuaweiSyscfgCombination, + i); + if (mode == combination->mode && acqorder == combination->acqorder) { + g_strfreev (split); + return combination; + } + } + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "No SYSCFG combination found matching the current one (%d,%d)", + mode, + acqorder); + g_strfreev (split); + return NULL; +} + +/*****************************************************************************/ +/* ^SYSCFGEX test parser */ + +static void +huawei_syscfgex_combination_free (MMHuaweiSyscfgexCombination *item) +{ + /* Just the contents, not the item itself! */ + g_free (item->mode_str); +} + +static gboolean +parse_mode_combination_string (const gchar *mode_str, + MMModemMode *allowed, + MMModemMode *preferred) +{ + guint n; + + if (g_str_equal (mode_str, "00")) { + *allowed = MM_MODEM_MODE_ANY; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + } + + *allowed = MM_MODEM_MODE_NONE; + *preferred = MM_MODEM_MODE_NONE; + + for (n = 0; n < strlen (mode_str); n+=2) { + MMModemMode mode; + + if (g_ascii_strncasecmp (&mode_str[n], "01", 2) == 0) + /* GSM */ + mode = MM_MODEM_MODE_2G; + else if (g_ascii_strncasecmp (&mode_str[n], "02", 2) == 0) + /* WCDMA */ + mode = MM_MODEM_MODE_3G; + else if (g_ascii_strncasecmp (&mode_str[n], "03", 2) == 0) + /* LTE */ + mode = MM_MODEM_MODE_4G; + else if (g_ascii_strncasecmp (&mode_str[n], "04", 2) == 0) + /* CDMA Note: no EV-DO, just return single value, so assume CDMA1x*/ + mode = MM_MODEM_MODE_2G; + else + mode = MM_MODEM_MODE_NONE; + + if (mode != MM_MODEM_MODE_NONE) { + /* The first one in the list is the preferred combination */ + if (n == 0) + *preferred |= mode; + *allowed |= mode; + } + } + + switch (mm_count_bits_set (*allowed)) { + case 0: + /* No allowed, error */ + return FALSE; + case 1: + /* If only one mode allowed, NONE preferred */ + *preferred = MM_MODEM_MODE_NONE; + /* fall through */ + default: + return TRUE; + } +} + +static GArray * +parse_mode_combination_string_list (const gchar *modes_str, + GError **error) +{ + GArray *supported_mode_combinations; + gchar **mode_combinations; + MMModemMode all = MM_MODEM_MODE_NONE; + gboolean has_all = FALSE; + guint i; + + mode_combinations = g_strsplit (modes_str, ",", -1); + supported_mode_combinations = g_array_sized_new (FALSE, + FALSE, + sizeof (MMHuaweiSyscfgexCombination), + g_strv_length (mode_combinations)); + g_array_set_clear_func (supported_mode_combinations, + (GDestroyNotify)huawei_syscfgex_combination_free); + + for (i = 0; mode_combinations[i]; i++) { + MMHuaweiSyscfgexCombination combination; + + mode_combinations[i] = mm_strip_quotes (mode_combinations[i]); + if (!parse_mode_combination_string (mode_combinations[i], + &combination.allowed, + &combination.preferred)) + continue; + + if (combination.allowed != MM_MODEM_MODE_ANY) { + combination.mode_str = g_strdup (mode_combinations[i]); + g_array_append_val (supported_mode_combinations, combination); + + all |= combination.allowed; + } else { + /* don't add the all_combination here, we may have more + * combinations in the loop afterwards */ + has_all = TRUE; + } + } + + g_strfreev (mode_combinations); + + /* Add here the all_combination */ + if (has_all) { + MMHuaweiSyscfgexCombination combination; + + combination.allowed = all; + combination.preferred = MM_MODEM_MODE_NONE; + combination.mode_str = g_strdup ("00"); + g_array_append_val (supported_mode_combinations, combination); + } + + /* If we didn't build a valid array of combinations, return an error */ + if (supported_mode_combinations->len == 0) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot parse list of allowed mode combinations: '%s'", + modes_str); + g_array_unref (supported_mode_combinations); + return NULL; + } + + return supported_mode_combinations; +} + +GArray * +mm_huawei_parse_syscfgex_test (const gchar *response, + GError **error) +{ + gchar **split; + GError *inner_error = NULL; + GArray *out; + + if (!response || !g_str_has_prefix (response, "^SYSCFGEX:")) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Missing ^SYSCFGEX prefix"); + return NULL; + } + + /* Examples: + * + * ^SYSCFGEX: ("00","03","02","01","99"), + * ((2000004e80380,"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100"), + * (3fffffff,"All Bands")), + * (0-3), + * (0-4), + * ((800c5,"LTE2100/LTE1800/LTE2600/LTE900/LTE800"), + * (7fffffffffffffff,"All bands")) + */ + split = split_groups (mm_strip_tag (response, "^SYSCFGEX:"), error); + if (!split) + return NULL; + + /* We expect 5 string chunks */ + if (g_strv_length (split) < 5) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unexpected ^SYSCFGEX format"); + g_strfreev (split); + return NULL; + } + + out = parse_mode_combination_string_list (split[0], &inner_error); + + g_strfreev (split); + + if (inner_error) { + g_propagate_error (error, inner_error); + return NULL; + } + + return out; +} + +/*****************************************************************************/ +/* ^SYSCFGEX response parser */ + +const MMHuaweiSyscfgexCombination * +mm_huawei_parse_syscfgex_response (const gchar *response, + const GArray *supported_mode_combinations, + GError **error) +{ + gchar **split; + guint i; + gsize len; + gchar *str; + + if (!response || !g_str_has_prefix (response, "^SYSCFGEX:")) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Missing ^SYSCFGEX prefix"); + return NULL; + } + + /* Format: + * + * ^SYSCFGEX: "00",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF + * ^SYSCFGEX: <mode>,<band>,<roam>,<srvdomain>,<lte-band> + */ + + response = mm_strip_tag (response, "^SYSCFGEX:"); + split = g_strsplit (response, ",", -1); + + /* We expect 5 string chunks */ + if (g_strv_length (split) < 5) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unexpected ^SYSCFGEX response format"); + g_strfreev (split); + return NULL; + } + + /* Unquote */ + str = split[0]; + len = strlen (str); + if ((len >= 2) && (str[0] == '"') && (str[len - 1] == '"')) { + str[0] = ' '; + str[len - 1] = ' '; + str = g_strstrip (str); + } + + /* Look for current modes among the supported ones */ + for (i = 0; i < supported_mode_combinations->len; i++) { + const MMHuaweiSyscfgexCombination *combination; + + combination = &g_array_index (supported_mode_combinations, + MMHuaweiSyscfgexCombination, + i); + if (g_str_equal (str, combination->mode_str)) { + g_strfreev (split); + return combination; + } + } + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "No SYSCFGEX combination found matching the current one (%s)", + str); + g_strfreev (split); + return NULL; +} + +/*****************************************************************************/ +/* ^NWTIME response parser */ + +gboolean +mm_huawei_parse_nwtime_response (const gchar *response, + gchar **iso8601p, + MMNetworkTimezone **tzp, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *match_error = NULL; + guint year = 0; + guint month = 0; + guint day = 0; + guint hour = 0; + guint minute = 0; + guint second = 0; + guint dt = 0; + gint tz = 0; + + g_assert (iso8601p || tzp); /* at least one */ + + r = g_regex_new ("\\^NWTIME:\\s*(\\d+)/(\\d+)/(\\d+),(\\d+):(\\d+):(\\d*)([\\-\\+\\d]+),(\\d+)$", 0, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse ^NWTIME results: "); + } else { + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't match ^NWTIME reply"); + } + return FALSE; + } + + /* Remember that g_match_info_get_match_count() includes match #0 */ + g_assert (g_match_info_get_match_count (match_info) >= 9); + + if (mm_get_uint_from_match_info (match_info, 1, &year) && + mm_get_uint_from_match_info (match_info, 2, &month) && + mm_get_uint_from_match_info (match_info, 3, &day) && + mm_get_uint_from_match_info (match_info, 4, &hour) && + mm_get_uint_from_match_info (match_info, 5, &minute) && + mm_get_uint_from_match_info (match_info, 6, &second) && + mm_get_int_from_match_info (match_info, 7, &tz) && + mm_get_uint_from_match_info (match_info, 8, &dt)) { + + /* adjust year */ + if (year < 100) + year += 2000; + /* + * tz = timezone offset in 15 minute intervals + * dt = daylight adjustment, 0 = none, 1 = 1 hour, 2 = 2 hours + * other values are marked reserved. + */ + if (tzp) { + *tzp = mm_network_timezone_new (); + mm_network_timezone_set_offset (*tzp, tz * 15); + mm_network_timezone_set_dst_offset (*tzp, dt * 60); + } + if (iso8601p) { + /* Return ISO-8601 format date/time string */ + *iso8601p = mm_new_iso8601_time (year, month, day, hour, + minute, second, + TRUE, (tz * 15) + (dt * 60), + error); + return (*iso8601p != NULL); + } + + return TRUE; + } + + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to parse ^NWTIME reply"); + return FALSE; +} + +/*****************************************************************************/ +/* ^TIME response parser */ + +gboolean +mm_huawei_parse_time_response (const gchar *response, + gchar **iso8601p, + MMNetworkTimezone **tzp, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *match_error = NULL; + guint year = 0; + guint month = 0; + guint day = 0; + guint hour = 0; + guint minute = 0; + guint second = 0; + + g_assert (iso8601p || tzp); /* at least one */ + + /* TIME response cannot ever provide TZ info */ + if (tzp) { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "^TIME does not provide timezone information"); + return FALSE; + } + + /* Already in ISO-8601 format, but verify just to be sure */ + r = g_regex_new ("\\^TIME:\\s*(\\d+)/(\\d+)/(\\d+)\\s*(\\d+):(\\d+):(\\d*)$", 0, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse ^TIME results: "); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match ^TIME reply"); + } + return FALSE; + } + + /* Remember that g_match_info_get_match_count() includes match #0 */ + g_assert (g_match_info_get_match_count (match_info) >= 7); + + if (mm_get_uint_from_match_info (match_info, 1, &year) && + mm_get_uint_from_match_info (match_info, 2, &month) && + mm_get_uint_from_match_info (match_info, 3, &day) && + mm_get_uint_from_match_info (match_info, 4, &hour) && + mm_get_uint_from_match_info (match_info, 5, &minute) && + mm_get_uint_from_match_info (match_info, 6, &second)) { + /* adjust year */ + if (year < 100) + year += 2000; + + /* Return ISO-8601 format date/time string */ + if (iso8601p) { + *iso8601p = mm_new_iso8601_time (year, month, day, hour, + minute, second, FALSE, 0, + error); + return (*iso8601p != NULL); + } + return TRUE; + } + + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to parse ^TIME reply"); + return FALSE; +} + +/*****************************************************************************/ +/* ^HCSQ response parser */ + +gboolean +mm_huawei_parse_hcsq_response (const gchar *response, + MMModemAccessTechnology *out_act, + guint *out_value1, + guint *out_value2, + guint *out_value3, + guint *out_value4, + guint *out_value5, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *match_error = NULL; + + r = g_regex_new ("\\^HCSQ:\\s*\"?([a-zA-Z]*)\"?,(\\d+),?(\\d+)?,?(\\d+)?,?(\\d+)?,?(\\d+)?$", 0, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse ^HCSQ results: "); + } else { + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't match ^HCSQ reply"); + } + return FALSE; + } + + /* Remember that g_match_info_get_match_count() includes match #0 */ + if (g_match_info_get_match_count (match_info) < 3) { + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Not enough elements in ^HCSQ reply"); + return FALSE; + } + + if (out_act) { + g_autofree gchar *s = NULL; + + s = g_match_info_fetch (match_info, 1); + *out_act = mm_string_to_access_tech (s); + } + + if (out_value1) + mm_get_uint_from_match_info (match_info, 2, out_value1); + if (out_value2) + mm_get_uint_from_match_info (match_info, 3, out_value2); + if (out_value3) + mm_get_uint_from_match_info (match_info, 4, out_value3); + if (out_value4) + mm_get_uint_from_match_info (match_info, 5, out_value4); + if (out_value5) + mm_get_uint_from_match_info (match_info, 6, out_value5); + + return TRUE; +} + +/*****************************************************************************/ +/* ^CVOICE response parser */ + +gboolean +mm_huawei_parse_cvoice_response (const gchar *response, + guint *out_hz, + guint *out_bits, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *match_error = NULL; + guint supported = 0; + guint hz = 0; + guint bits = 0; + + /* ^CVOICE: <0=supported,1=unsupported>,<hz>,<bits>,<unknown> */ + r = g_regex_new ("\\^CVOICE:\\s*(\\d)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)$", 0, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse ^CVOICE results: "); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match ^CVOICE reply"); + } + return FALSE; + } + + /* Remember that g_match_info_get_match_count() includes match #0 */ + g_assert (g_match_info_get_match_count (match_info) >= 5); + + if (mm_get_uint_from_match_info (match_info, 1, &supported) && + mm_get_uint_from_match_info (match_info, 2, &hz) && + mm_get_uint_from_match_info (match_info, 3, &bits)) { + if (supported == 0) { + if (out_hz) + *out_hz = hz; + if (out_bits) + *out_bits = bits; + return TRUE; + } + + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "^CVOICE not supported by this device"); + return FALSE; + } + + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to parse ^CVOICE reply"); + return FALSE; +} + +/*****************************************************************************/ +/* ^GETPORTMODE response parser */ + +#define GETPORTMODE_PREFIX "^GETPORTMODE:" + +GArray * +mm_huawei_parse_getportmode_response (const gchar *response, + gpointer log_object, + GError **error) +{ + g_autoptr(GArray) modes = NULL; + g_auto(GStrv) split = NULL; + guint i; + gint n_items; + + split = g_strsplit (response, ",", -1); + n_items = g_strv_length (split) - 1; + if (n_items < 1) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unexpected number of items in response"); + return NULL; + } + + /* validate response prefix */ + if (g_ascii_strncasecmp (split[0], GETPORTMODE_PREFIX, strlen (GETPORTMODE_PREFIX)) != 0) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unexpected response prefix"); + return NULL; + } + + mm_obj_dbg (log_object, "processing ^GETPORTMODE response..."); + + modes = g_array_sized_new (FALSE, FALSE, sizeof (MMHuaweiPortMode), n_items); + + /* iterate all port items found */ + for (i = 1; split[i]; i++) { + MMHuaweiPortMode mode = MM_HUAWEI_PORT_MODE_NONE; + gchar *separator; + guint port_number; + + separator = strchr (split[i], ':'); + if (!separator) + continue; + + /* the reported port number may start either by 0 or by 1; the important + * thing is therefore no the number itself, only that it's a number */ + g_strstrip (&separator[1]); + if (!mm_get_uint_from_str (&separator[1], &port_number)) { + mm_obj_warn (log_object, " couldn't parse port number: %s", split[i]); + break; + } + + *separator = '\0'; + g_strstrip (split[i]); + + if (g_ascii_strcasecmp (split[i], "pcui") == 0) + mode = MM_HUAWEI_PORT_MODE_PCUI; + else if ((g_ascii_strcasecmp (split[i], "mdm") == 0) || + (g_ascii_strcasecmp (split[i], "modem") == 0) || + (g_ascii_strcasecmp (split[i], "3g_modem") == 0)) + mode = MM_HUAWEI_PORT_MODE_MODEM; + else if ((g_ascii_strcasecmp (split[i], "diag") == 0) || + (g_ascii_strcasecmp (split[i], "3g_diag") == 0) || + (g_ascii_strcasecmp (split[i], "4g_diag") == 0)) + mode = MM_HUAWEI_PORT_MODE_DIAG; + else if (g_ascii_strcasecmp (split[i], "gps") == 0) + mode = MM_HUAWEI_PORT_MODE_GPS; + else if ((g_ascii_strcasecmp (split[i], "ndis") == 0) || + (g_ascii_strcasecmp (split[i], "rndis") == 0) || + (g_ascii_strcasecmp (split[i], "ncm") == 0) || + (g_ascii_strcasecmp (split[i], "ecm") == 0)) + mode = MM_HUAWEI_PORT_MODE_NET; + else if (g_ascii_strcasecmp (split[i], "cdrom") == 0) + mode = MM_HUAWEI_PORT_MODE_CDROM; + else if ((g_ascii_strcasecmp (split[i], "sd") == 0) || + (g_ascii_strncasecmp (split[i], "mass", 4) == 0)) + mode = MM_HUAWEI_PORT_MODE_SD; + else if (g_ascii_strcasecmp (split[i], "bt") == 0) + mode = MM_HUAWEI_PORT_MODE_BT; + else if ((g_ascii_strcasecmp (split[i], "a_shell") == 0) || + (g_ascii_strcasecmp (split[i], "c_shell") == 0)) + mode = MM_HUAWEI_PORT_MODE_SHELL; + + mm_obj_dbg (log_object, " port mode %s reported at port number %u", + mm_huawei_port_mode_get_string (mode), port_number); + g_array_append_val (modes, mode); + } + + if (!modes->len) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No port modes loaded"); + return NULL; + } + + return g_steal_pointer (&modes); +} diff --git a/src/plugins/huawei/mm-modem-helpers-huawei.h b/src/plugins/huawei/mm-modem-helpers-huawei.h new file mode 100644 index 00000000..3d1a4b22 --- /dev/null +++ b/src/plugins/huawei/mm-modem-helpers-huawei.h @@ -0,0 +1,193 @@ +/* -*- 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) 2013 Huawei Technologies Co., Ltd + * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_MODEM_HELPERS_HUAWEI_H +#define MM_MODEM_HELPERS_HUAWEI_H + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +/*****************************************************************************/ +/* ^NDISSTAT / ^NDISSTATQRY response parser */ +gboolean mm_huawei_parse_ndisstatqry_response (const gchar *response, + gboolean *ipv4_available, + gboolean *ipv4_connected, + gboolean *ipv6_available, + gboolean *ipv6_connected, + GError **error); + +/*****************************************************************************/ +/* ^DHCP response parser */ +gboolean mm_huawei_parse_dhcp_response (const char *reply, + guint *out_address, + guint *out_prefix, + guint *out_gateway, + guint *out_dns1, + guint *out_dns2, + GError **error); + +/*****************************************************************************/ +/* ^SYSINFO response parser */ +gboolean mm_huawei_parse_sysinfo_response (const char *reply, + guint *out_srv_status, + guint *out_srv_domain, + guint *out_roam_status, + guint *out_sys_mode, + guint *out_sim_state, + gboolean *out_sys_submode_valid, + guint *out_sys_submode, + GError **error); + +/*****************************************************************************/ +/* ^SYSINFOEX response parser */ +gboolean mm_huawei_parse_sysinfoex_response (const char *reply, + guint *out_srv_status, + guint *out_srv_domain, + guint *out_roam_status, + guint *out_sim_state, + guint *out_sys_mode, + guint *out_sys_submode, + GError **error); + +/*****************************************************************************/ +/* ^PREFMODE test parser */ + +typedef struct { + guint prefmode; + MMModemMode allowed; + MMModemMode preferred; +} MMHuaweiPrefmodeCombination; + +GArray *mm_huawei_parse_prefmode_test (const gchar *response, + gpointer log_object, + GError **error); + +/*****************************************************************************/ +/* ^PREFMODE response parser */ + +const MMHuaweiPrefmodeCombination *mm_huawei_parse_prefmode_response (const gchar *response, + const GArray *supported_mode_combinations, + GError **error); + +/*****************************************************************************/ +/* ^SYSCFG test parser */ + +/* This is the default string we use as fallback when the modem gives + * an empty response to AT^SYSCFG=? */ +#define MM_HUAWEI_DEFAULT_SYSCFG_FMT "^SYSCFG:(2,13,14,16),(0-3),,," + +typedef struct { + guint mode; + guint acqorder; + MMModemMode allowed; + MMModemMode preferred; +} MMHuaweiSyscfgCombination; + +GArray *mm_huawei_parse_syscfg_test (const gchar *response, + gpointer log_object, + GError **error); + +/*****************************************************************************/ +/* ^SYSCFG response parser */ + +const MMHuaweiSyscfgCombination *mm_huawei_parse_syscfg_response (const gchar *response, + const GArray *supported_mode_combinations, + GError **error); + +/*****************************************************************************/ +/* ^SYSCFGEX test parser */ + +typedef struct { + gchar *mode_str; + MMModemMode allowed; + MMModemMode preferred; +} MMHuaweiSyscfgexCombination; + +GArray *mm_huawei_parse_syscfgex_test (const gchar *response, + GError **error); + +/*****************************************************************************/ +/* ^SYSCFGEX response parser */ + +const MMHuaweiSyscfgexCombination *mm_huawei_parse_syscfgex_response (const gchar *response, + const GArray *supported_mode_combinations, + GError **error); + +/*****************************************************************************/ +/* ^NWTIME response parser */ + +gboolean mm_huawei_parse_nwtime_response (const gchar *response, + gchar **iso8601p, + MMNetworkTimezone **tzp, + GError **error); + +/*****************************************************************************/ +/* ^TIME response parser */ + +gboolean mm_huawei_parse_time_response (const gchar *response, + gchar **iso8601p, + MMNetworkTimezone **tzp, + GError **error); + +/*****************************************************************************/ +/* ^HCSQ response parser */ + +gboolean mm_huawei_parse_hcsq_response (const gchar *response, + MMModemAccessTechnology *out_act, + guint *out_value1, + guint *out_value2, + guint *out_value3, + guint *out_value4, + guint *out_value5, + GError **error); + +/*****************************************************************************/ +/* ^CVOICE response parser */ + +gboolean mm_huawei_parse_cvoice_response (const gchar *response, + guint *hz, + guint *bits, + GError **error); + +/*****************************************************************************/ +/* ^GETPORTMODE response parser */ + +typedef enum { /*< underscore_name=mm_huawei_port_mode >*/ + MM_HUAWEI_PORT_MODE_NONE, + MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_MODEM, + MM_HUAWEI_PORT_MODE_DIAG, + MM_HUAWEI_PORT_MODE_GPS, + MM_HUAWEI_PORT_MODE_NET, + MM_HUAWEI_PORT_MODE_CDROM, + MM_HUAWEI_PORT_MODE_SD, + MM_HUAWEI_PORT_MODE_BT, + MM_HUAWEI_PORT_MODE_SHELL, +} MMHuaweiPortMode; + +#define MM_HUAWEI_PORT_MODE_IS_SERIAL(mode) \ + (mode == MM_HUAWEI_PORT_MODE_PCUI || \ + mode == MM_HUAWEI_PORT_MODE_MODEM || \ + mode == MM_HUAWEI_PORT_MODE_DIAG || \ + mode == MM_HUAWEI_PORT_MODE_GPS || \ + mode == MM_HUAWEI_PORT_MODE_SHELL) + +GArray *mm_huawei_parse_getportmode_response (const gchar *response, + gpointer log_object, + GError **error); + +#endif /* MM_MODEM_HELPERS_HUAWEI_H */ diff --git a/src/plugins/huawei/mm-plugin-huawei.c b/src/plugins/huawei/mm-plugin-huawei.c new file mode 100644 index 00000000..ad8d32b0 --- /dev/null +++ b/src/plugins/huawei/mm-plugin-huawei.c @@ -0,0 +1,735 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <gmodule.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include <ModemManager-tags.h> +#include "mm-port-enums-types.h" +#include "mm-log.h" +#include "mm-plugin-huawei.h" +#include "mm-broadband-modem-huawei.h" +#include "mm-modem-helpers-huawei.h" +#include "mm-huawei-enums-types.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi.h" +#endif + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#endif + +G_DEFINE_TYPE (MMPluginHuawei, mm_plugin_huawei, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ +/* Custom init */ + +#define TAG_FIRST_INTERFACE_CONTEXT "first-interface-context" + +/* Maximum time to wait for the first interface 0 to appear and get probed. + * If it doesn't appear in this time, we'll decide which will be considered the + * first interface. */ +#define MAX_WAIT_TIME 5 + +typedef struct { + MMPortProbe *probe; + gint first_usbif; + guint timeout_id; + gboolean custom_init_run; +} FirstInterfaceContext; + +static void +first_interface_context_free (FirstInterfaceContext *ctx) +{ + if (ctx->timeout_id) + g_source_remove (ctx->timeout_id); + g_object_unref (ctx->probe); + g_slice_free (FirstInterfaceContext, ctx); +} + +#define TAG_GETPORTMODE_RESULT "getportmode-result" +#define TAG_AT_PORT_FLAGS "at-port-flags" + +typedef struct { + MMPortSerialAt *port; + gboolean curc_done; + guint curc_retries; + gboolean getportmode_done; + guint getportmode_retries; +} HuaweiCustomInitContext; + +static void +huawei_custom_init_context_free (HuaweiCustomInitContext *ctx) +{ + g_object_unref (ctx->port); + g_slice_free (HuaweiCustomInitContext, ctx); +} + +static gboolean +huawei_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void huawei_custom_init_step (GTask *task); + +static void +getportmode_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + MMDevice *device; + MMPortProbe *probe; + HuaweiCustomInitContext *ctx; + const gchar *response; + GArray *modes; + g_autoptr(GError) error = NULL; + + probe = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + device = mm_port_probe_peek_device (probe); + + response = mm_port_serial_at_command_finish (port, res, &error); + if (error) { + mm_obj_dbg (probe, "couldn't get port mode: '%s'", error->message); + + /* If any error occurred that was not ERROR or COMMAND NOT SUPPORT then + * retry the command. + */ + if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) + ctx->getportmode_done = TRUE; + huawei_custom_init_step (task); + return; + } + + /* Mark port as being AT already */ + mm_port_probe_set_result_at (probe, TRUE); + + /* Flag as GETPORTMODE already done */ + ctx->getportmode_done = TRUE; + + modes = mm_huawei_parse_getportmode_response (response, probe, &error); + if (!modes) + mm_obj_warn (probe, "failed to parse ^GETPORTMODE response: %s", error->message); + else + g_object_set_data_full (G_OBJECT (device), TAG_GETPORTMODE_RESULT, modes, (GDestroyNotify) g_array_unref); + huawei_custom_init_step (task); +} + +static void +curc_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + MMPortProbe *probe; + HuaweiCustomInitContext *ctx; + g_autoptr(GError) error = NULL; + + probe = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + mm_port_serial_at_command_finish (port, res, &error); + if (error) { + /* Retry if we get a timeout error */ + if (g_error_matches (error, + MM_SERIAL_ERROR, + MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) + goto out; + + mm_obj_dbg (probe, "couldn't turn off unsolicited messages in secondary ports: %s", error->message); + } + + mm_obj_dbg (probe, "unsolicited messages in secondary ports turned off"); + + ctx->curc_done = TRUE; + +out: + huawei_custom_init_step (task); +} + +static void +try_next_usbif (MMPortProbe *probe, + MMDevice *device) +{ + FirstInterfaceContext *fi_ctx; + GList *l; + gint closest; + + fi_ctx = g_object_get_data (G_OBJECT (device), TAG_FIRST_INTERFACE_CONTEXT); + g_assert (fi_ctx != NULL); + + /* Look for the next closest one among the list of interfaces in the device, + * and enable that one as being first */ + closest = G_MAXINT; + for (l = mm_device_peek_port_probe_list (device); l; l = g_list_next (l)) { + MMPortProbe *iter = MM_PORT_PROBE (l->data); + + /* Only expect ttys for next probing attempt */ + if (g_str_equal (mm_port_probe_get_port_subsys (iter), "tty")) { + gint usbif; + + usbif = mm_kernel_device_get_interface_number (mm_port_probe_peek_port (iter)); + if (usbif == fi_ctx->first_usbif) { + /* This is the one we just probed, which wasn't yet removed, so just skip it */ + } else if (usbif > fi_ctx->first_usbif && + usbif < closest) { + closest = usbif; + } + } + } + + if (closest == G_MAXINT) { + /* No more ttys to try! Just return something */ + closest = 0; + mm_obj_dbg (probe, "no more ports to run initial probing"); + } else + mm_obj_dbg (probe, "will try initial probing with interface '%d' instead", closest); + + fi_ctx->first_usbif = closest; +} + +static void +huawei_custom_init_step (GTask *task) +{ + MMPortProbe *probe; + HuaweiCustomInitContext *ctx; + FirstInterfaceContext *fi_ctx; + MMKernelDevice *port; + + probe = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* If cancelled, end */ + if (g_task_return_error_if_cancelled (task)) { + mm_obj_dbg (probe, "no need to keep on running custom init"); + g_object_unref (task); + return; + } + + if (!ctx->curc_done) { + if (ctx->curc_retries == 0) { + /* All retries consumed, probably not an AT port */ + mm_port_probe_set_result_at (probe, FALSE); + /* Try with next */ + try_next_usbif (probe, mm_port_probe_peek_device (probe)); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + ctx->curc_retries--; + /* Turn off unsolicited messages on secondary ports until needed */ + mm_port_serial_at_command ( + ctx->port, + "AT^CURC=0", + 3, + FALSE, /* raw */ + FALSE, /* allow_cached */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)curc_ready, + task); + return; + } + + /* Try to get a port map from the modem */ + port = mm_port_probe_peek_port (probe); + if (!ctx->getportmode_done && !mm_kernel_device_get_global_property_as_boolean (port, "ID_MM_HUAWEI_DISABLE_GETPORTMODE")) { + if (ctx->getportmode_retries == 0) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + ctx->getportmode_retries--; + mm_port_serial_at_command ( + ctx->port, + "AT^GETPORTMODE", + 3, + FALSE, /* raw */ + FALSE, /* allow_cached */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)getportmode_ready, + task); + return; + } + + /* All done it seems */ + fi_ctx = g_object_get_data (G_OBJECT (mm_port_probe_peek_device (probe)), TAG_FIRST_INTERFACE_CONTEXT); + g_assert (fi_ctx != NULL); + fi_ctx->custom_init_run = TRUE; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static gboolean +first_interface_missing_timeout_cb (MMDevice *device) +{ + FirstInterfaceContext *fi_ctx; + + fi_ctx = g_object_get_data (G_OBJECT (device), TAG_FIRST_INTERFACE_CONTEXT); + g_assert (fi_ctx != NULL); + try_next_usbif (fi_ctx->probe, device); + + /* Reload the timeout, just in case we end up not having the next interface to probe... + * which is anyway very unlikely as we got it by looking at the real probe list, but anyway... */ + return G_SOURCE_CONTINUE; +} + +static void +huawei_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMDevice *device; + FirstInterfaceContext *fi_ctx; + HuaweiCustomInitContext *ctx; + GTask *task; + + device = mm_port_probe_peek_device (probe); + + /* The primary port (called the "modem" port in the Windows drivers) is + * always USB interface 0, and we need to detect that interface first for + * two reasons: (1) to disable unsolicited messages on other ports that + * may fill up the buffer and crash the device, and (2) to attempt to get + * the port layout for hints about what the secondary port is (called the + * "pcui" port in Windows). Thus we probe USB interface 0 first and defer + * probing other interfaces until we've got if0, at which point we allow + * the other ports to be probed too. + */ + fi_ctx = g_object_get_data (G_OBJECT (device), TAG_FIRST_INTERFACE_CONTEXT); + if (!fi_ctx) { + /* This is the first time we ask for the context. Set it up. */ + fi_ctx = g_slice_new0 (FirstInterfaceContext); + fi_ctx->probe = g_object_ref (probe); + g_object_set_data_full (G_OBJECT (device), + TAG_FIRST_INTERFACE_CONTEXT, + fi_ctx, + (GDestroyNotify)first_interface_context_free); + /* The timeout is controlled in the data set in 'device', and therefore + * it should be safe to assume that the timeout will not get fired after + * having disposed 'device' */ + fi_ctx->timeout_id = g_timeout_add_seconds (MAX_WAIT_TIME, + (GSourceFunc)first_interface_missing_timeout_cb, + device); + + /* By default, we'll ask the Huawei plugin to start probing usbif 0 */ + fi_ctx->first_usbif = 0; + + /* Custom init of the Huawei plugin is to be run only in the first + * interface. We'll control here whether we did run it already or not. */ + fi_ctx->custom_init_run = FALSE; + } + + ctx = g_slice_new (HuaweiCustomInitContext); + ctx->port = g_object_ref (port); + ctx->curc_done = FALSE; + ctx->curc_retries = 3; + ctx->getportmode_done = FALSE; + ctx->getportmode_retries = 3; + + task = g_task_new (probe, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)huawei_custom_init_context_free); + + /* Custom init only to be run in the first interface */ + if (mm_kernel_device_get_interface_number (mm_port_probe_peek_port (probe)) != fi_ctx->first_usbif) { + if (fi_ctx->custom_init_run) + /* If custom init was run already, we can consider this as successfully run */ + g_task_return_boolean (task, TRUE); + else + /* Otherwise, we'll need to defer the probing a bit more */ + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_RETRY, + "Defer needed"); + + g_object_unref (task); + return; + } + + /* We can run custom init in the first interface! clear the timeout as it is no longer needed */ + if (fi_ctx->timeout_id) { + g_source_remove (fi_ctx->timeout_id); + fi_ctx->timeout_id = 0; + } + + huawei_custom_init_step (task); +} + +/*****************************************************************************/ + +static gint +probe_cmp_by_usbif (MMPortProbe *a, + MMPortProbe *b) +{ + return (mm_kernel_device_get_interface_number (mm_port_probe_peek_port (a)) - + mm_kernel_device_get_interface_number (mm_port_probe_peek_port (b))); +} + +static guint +propagate_getportmode_hints (MMPlugin *self, + GList *probes, + gboolean *primary_flagged) +{ + MMDevice *device; + GArray *modes; + GList *l; + GList *tty_probes = NULL; + guint n_ports_with_hints = 0; + guint mode_i = 0; + + g_assert (probes != NULL); + device = mm_port_probe_peek_device (MM_PORT_PROBE (probes->data)); + modes = g_object_get_data (G_OBJECT (device), TAG_GETPORTMODE_RESULT); + + /* Nothing to do if GETPORTMODE is flagged as not supported */ + if (!modes) + return 0; + + /* Build a list of TTY port probes (AT and not-AT) sorted by interface number */ + for (l = probes; l; l = g_list_next (l)) { + MMPortProbe *probe; + + probe = MM_PORT_PROBE (l->data); + if (g_str_equal (mm_port_probe_get_port_subsys (probe), "tty")) + tty_probes = g_list_insert_sorted (tty_probes, probe, (GCompareFunc) probe_cmp_by_usbif); + } + + /* Propagate the getportmode tags to the specific port probes */ + for (l = tty_probes, mode_i = 0; l; l = g_list_next (l)) { + MMPortProbe *probe; + MMPortSerialAtFlag at_port_flags = MM_PORT_SERIAL_AT_FLAG_NONE; + MMHuaweiPortMode port_mode; + + probe = MM_PORT_PROBE (l->data); + + /* Look for the next serial port mode applicable */ + while (!MM_HUAWEI_PORT_MODE_IS_SERIAL (g_array_index (modes, MMHuaweiPortMode, mode_i)) && (mode_i < modes->len)) + mode_i++; + if (mode_i == modes->len) { + mm_obj_dbg (probe, "missing port mode hint"); + continue; + } + + port_mode = g_array_index (modes, MMHuaweiPortMode, mode_i); + if (!mm_port_probe_is_at (probe)) { + mm_obj_dbg (probe, "port mode hint for non-AT port: %s", mm_huawei_port_mode_get_string (port_mode)); + mode_i++; + continue; + } + + mm_obj_dbg (probe, "port mode hint for AT port: %s", mm_huawei_port_mode_get_string (port_mode)); + if (port_mode == MM_HUAWEI_PORT_MODE_PCUI) + at_port_flags = MM_PORT_SERIAL_AT_FLAG_PRIMARY; + else if (port_mode == MM_HUAWEI_PORT_MODE_MODEM) + at_port_flags = MM_PORT_SERIAL_AT_FLAG_PPP; + + if (at_port_flags != MM_PORT_SERIAL_AT_FLAG_NONE) { + n_ports_with_hints++; + g_object_set_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS, GUINT_TO_POINTER (at_port_flags)); + } + mode_i++; + } + + g_list_free (tty_probes); + + return n_ports_with_hints; +} + +static guint +propagate_description_hints (MMPlugin *self, + GList *probes, + gboolean *primary_flagged) +{ + GList *l; + guint n_ports_with_hints = 0; + + for (l = probes; l; l = g_list_next (l)) { + MMPortProbe *probe; + MMPortSerialAtFlag at_port_flags = MM_PORT_SERIAL_AT_FLAG_NONE; + const gchar *description; + g_autofree gchar *lower_description = NULL; + + probe = MM_PORT_PROBE (l->data); + + if (!mm_port_probe_is_at (probe)) + continue; + + description = mm_kernel_device_get_interface_description (mm_port_probe_peek_port (probe)); + if (!description) + continue; + + mm_obj_dbg (probe, "%s interface description: %s", mm_port_probe_get_port_name (probe), description); + + lower_description = g_ascii_strdown (description, -1); + if (strstr (lower_description, "modem")) + at_port_flags = MM_PORT_SERIAL_AT_FLAG_PPP; + else if (strstr (lower_description, "pcui")) { + at_port_flags = MM_PORT_SERIAL_AT_FLAG_PRIMARY; + *primary_flagged = TRUE; + } + + if (at_port_flags != MM_PORT_SERIAL_AT_FLAG_NONE) { + n_ports_with_hints++; + g_object_set_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS, GUINT_TO_POINTER (at_port_flags)); + } + } + + return n_ports_with_hints; +} + +static guint +propagate_generic_hints (MMPlugin *self, + GList *probes, + gboolean *primary_flagged) +{ + GList *l; + guint n_ports_with_hints = 0; + + for (l = probes; l; l = g_list_next (l)) { + MMPortProbe *probe; + MMKernelDevice *kernel_device; + MMPortSerialAtFlag at_port_flags = MM_PORT_SERIAL_AT_FLAG_NONE; + + probe = MM_PORT_PROBE (l->data); + + if (!mm_port_probe_is_at (probe)) + continue; + + kernel_device = mm_port_probe_peek_port (probe); + if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_PORT_TYPE_AT_PRIMARY)) { + at_port_flags = MM_PORT_SERIAL_AT_FLAG_PRIMARY; + *primary_flagged = TRUE; + } + else if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_PORT_TYPE_AT_SECONDARY)) + at_port_flags = MM_PORT_SERIAL_AT_FLAG_SECONDARY; + else if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_PORT_TYPE_AT_PPP)) + at_port_flags = MM_PORT_SERIAL_AT_FLAG_PPP; + + if (at_port_flags != MM_PORT_SERIAL_AT_FLAG_NONE) { + n_ports_with_hints++; + g_object_set_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS, GUINT_TO_POINTER (at_port_flags)); + } + } + + return n_ports_with_hints; +} + +static guint +fallback_primary_cdcwdm (MMPlugin *self, + GList *probes) +{ + GList *l; + + for (l = probes; l; l = g_list_next (l)) { + MMPortProbe *probe; + + probe = MM_PORT_PROBE (l->data); + + if (!mm_port_probe_is_at (probe)) + continue; + + if (g_str_equal (mm_port_probe_get_port_subsys (probe), "usbmisc")) { + mm_obj_dbg (self, "fallback port type hint applied to first cdc-wmd port found"); + g_object_set_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS, GUINT_TO_POINTER (MM_PORT_SERIAL_AT_FLAG_PRIMARY)); + return 1; + } + } + return 0; +} + +static guint +fallback_usbif0 (MMPlugin *self, + GList *probes) +{ + GList *l; + + for (l = probes; l; l = g_list_next (l)) { + MMPortProbe *probe; + guint usbif; + + probe = MM_PORT_PROBE (l->data); + + if (!mm_port_probe_is_at (probe)) + continue; + + usbif = mm_kernel_device_get_property_as_int_hex (mm_port_probe_peek_port (probe), "ID_USB_INTERFACE_NUM"); + if (usbif == 0) { + mm_obj_dbg (self, "fallback port type hint applied to interface 0"); + g_object_set_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS, GUINT_TO_POINTER (MM_PORT_SERIAL_AT_FLAG_PPP)); + return 1; + } + } + return 0; +} + +static void +propagate_port_type_hints (MMPlugin *self, + GList *probes) +{ + gboolean primary_flagged = FALSE; + guint n_ports_with_hints; + + g_assert (probes != NULL); + + if ((n_ports_with_hints = propagate_getportmode_hints (self, probes, &primary_flagged)) > 0) + mm_obj_dbg (self, "port type hints set by GETPORTMODE"); + else if ((n_ports_with_hints = propagate_description_hints (self, probes, &primary_flagged)) > 0) + mm_obj_dbg (self, "port type hints set by interface descriptions"); + else if ((n_ports_with_hints = propagate_generic_hints (self, probes, &primary_flagged)) > 0) + mm_obj_dbg (self, "port type hints set by generic udev tags"); + + /* Fallback hint for the first cdc-wdm port if no other port has been flagged as primary */ + if (!primary_flagged) + n_ports_with_hints += fallback_primary_cdcwdm (self, probes); + + /* If not a single port type hint available (not plugin-provided and not generic) + * then we'll assume usbif 0 is the modem port */ + if (!n_ports_with_hints) + n_ports_with_hints = fallback_usbif0 (self, probes); + + mm_obj_dbg (self, "%u port hints have been set", n_ports_with_hints); +} + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + propagate_port_type_hints (self, probes); + +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Huawei modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered Huawei modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_huawei_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +static gboolean +grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + MMPortSerialAtFlag pflags; + MMKernelDevice *port; + MMPortType port_type; + + port_type = mm_port_probe_get_port_type (probe); + port = mm_port_probe_peek_port (probe); + + pflags = (MMPortSerialAtFlag) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS)); + if (pflags != MM_PORT_SERIAL_AT_FLAG_NONE) { + gchar *str; + + str = mm_port_serial_at_flag_build_string_from_mask (pflags); + mm_obj_dbg (self, "(%s/%s) port will have AT flags '%s'", + mm_port_probe_get_port_subsys (probe), + mm_port_probe_get_port_name (probe), + str); + g_free (str); + } else { + /* The huawei plugin handles the generic udev tags itself, so explicitly request + * to avoid processing them by the generic modem. */ + pflags = MM_PORT_SERIAL_AT_FLAG_NONE_NO_GENERIC; + } + + return mm_base_modem_grab_port (modem, + port, + port_type, + pflags, + error); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x12d1, 0 }; + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (huawei_custom_init), + .finish = G_CALLBACK (huawei_custom_init_finish), + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_HUAWEI, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_REQUIRED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + NULL)); +} + +static void +mm_plugin_huawei_init (MMPluginHuawei *self) +{ +} + +static void +mm_plugin_huawei_class_init (MMPluginHuaweiClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = grab_port; +} diff --git a/src/plugins/huawei/mm-plugin-huawei.h b/src/plugins/huawei/mm-plugin-huawei.h new file mode 100644 index 00000000..daba2055 --- /dev/null +++ b/src/plugins/huawei/mm-plugin-huawei.h @@ -0,0 +1,42 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org + */ + +#ifndef MM_PLUGIN_HUAWEI_H +#define MM_PLUGIN_HUAWEI_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_HUAWEI (mm_plugin_huawei_get_type ()) +#define MM_PLUGIN_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_HUAWEI, MMPluginHuawei)) +#define MM_PLUGIN_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_HUAWEI, MMPluginHuaweiClass)) +#define MM_IS_PLUGIN_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_HUAWEI)) +#define MM_IS_PLUGIN_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_HUAWEI)) +#define MM_PLUGIN_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_HUAWEI, MMPluginHuaweiClass)) + +typedef struct { + MMPlugin parent; +} MMPluginHuawei; + +typedef struct { + MMPluginClass parent; +} MMPluginHuaweiClass; + +GType mm_plugin_huawei_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_HUAWEI_H */ diff --git a/src/plugins/huawei/mm-sim-huawei.c b/src/plugins/huawei/mm-sim-huawei.c new file mode 100644 index 00000000..f937c773 --- /dev/null +++ b/src/plugins/huawei/mm-sim-huawei.c @@ -0,0 +1,167 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> +#include "mm-modem-helpers.h" +#include "mm-base-modem-at.h" + +#include "mm-sim-huawei.h" + +G_DEFINE_TYPE (MMSimHuawei, mm_sim_huawei, MM_TYPE_BASE_SIM) + +/*****************************************************************************/ +/* SIM identifier loading */ + +static gchar * +load_sim_identifier_finish (MMBaseSim *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_sim_identifier_ready (MMSimHuawei *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + gchar *simid; + + simid = MM_BASE_SIM_CLASS (mm_sim_huawei_parent_class)->load_sim_identifier_finish (MM_BASE_SIM (self), res, &error); + if (simid) + g_task_return_pointer (task, simid, g_free); + else + g_task_return_error (task, error); + + g_object_unref (task); +} + +static void +iccid_read_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBaseSim *self; + const gchar *response; + const gchar *p; + char *parsed; + + response = mm_base_modem_at_command_finish (modem, res, NULL); + if (!response) + goto error; + + p = mm_strip_tag (response, "^ICCID:"); + if (!p) + goto error; + + parsed = mm_3gpp_parse_iccid (p, NULL); + if (parsed) { + g_task_return_pointer (task, parsed, g_free); + g_object_unref (task); + return; + } + +error: + /* Chain up to parent method; older devices don't support ^ICCID */ + self = g_task_get_source_object (task); + MM_BASE_SIM_CLASS (mm_sim_huawei_parent_class)->load_sim_identifier (self, + (GAsyncReadyCallback) parent_load_sim_identifier_ready, + task); +} + +static void +load_sim_identifier (MMBaseSim *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBaseModem *modem = NULL; + + g_object_get (self, + MM_BASE_SIM_MODEM, &modem, + NULL); + + mm_base_modem_at_command ( + modem, + "^ICCID?", + 5, + FALSE, + (GAsyncReadyCallback)iccid_read_ready, + g_task_new (self, NULL, callback, user_data)); + g_object_unref (modem); +} + +/*****************************************************************************/ + +MMBaseSim * +mm_sim_huawei_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *sim; + + source = g_async_result_get_source_object (res); + sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!sim) + return NULL; + + /* Only export valid SIMs */ + mm_base_sim_export (MM_BASE_SIM (sim)); + + return MM_BASE_SIM (sim); +} + +void +mm_sim_huawei_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (MM_TYPE_SIM_HUAWEI, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_SIM_MODEM, modem, + "active", TRUE, /* by default always active */ + NULL); +} + +static void +mm_sim_huawei_init (MMSimHuawei *self) +{ +} + +static void +mm_sim_huawei_class_init (MMSimHuaweiClass *klass) +{ + MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass); + + base_sim_class->load_sim_identifier = load_sim_identifier; + base_sim_class->load_sim_identifier_finish = load_sim_identifier_finish; +} diff --git a/src/plugins/huawei/mm-sim-huawei.h b/src/plugins/huawei/mm-sim-huawei.h new file mode 100644 index 00000000..eeeefbaf --- /dev/null +++ b/src/plugins/huawei/mm-sim-huawei.h @@ -0,0 +1,53 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#ifndef MM_SIM_HUAWEI_H +#define MM_SIM_HUAWEI_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-sim.h" + +#define MM_TYPE_SIM_HUAWEI (mm_sim_huawei_get_type ()) +#define MM_SIM_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_HUAWEI, MMSimHuawei)) +#define MM_SIM_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_HUAWEI, MMSimHuaweiClass)) +#define MM_IS_SIM_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_HUAWEI)) +#define MM_IS_SIM_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_HUAWEI)) +#define MM_SIM_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_HUAWEI, MMSimHuaweiClass)) + +typedef struct _MMSimHuawei MMSimHuawei; +typedef struct _MMSimHuaweiClass MMSimHuaweiClass; + +struct _MMSimHuawei { + MMBaseSim parent; +}; + +struct _MMSimHuaweiClass { + MMBaseSimClass parent; +}; + +GType mm_sim_huawei_get_type (void); + +void mm_sim_huawei_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_sim_huawei_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_SIM_HUAWEI_H */ diff --git a/src/plugins/huawei/tests/test-modem-helpers-huawei.c b/src/plugins/huawei/tests/test-modem-helpers-huawei.c new file mode 100644 index 00000000..e6f2490b --- /dev/null +++ b/src/plugins/huawei/tests/test-modem-helpers-huawei.c @@ -0,0 +1,1422 @@ +/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-huawei.h" + +/*****************************************************************************/ +/* Test ^NDISSTAT / ^NDISSTATQRY responses */ + +typedef struct { + const gchar *str; + gboolean expected_ipv4_available; + gboolean expected_ipv4_connected; + gboolean expected_ipv6_available; + gboolean expected_ipv6_connected; +} NdisstatqryTest; + +static const NdisstatqryTest ndisstatqry_tests[] = { + { "^NDISSTAT: 1,,,IPV4\r\n", TRUE, TRUE, FALSE, FALSE }, + { "^NDISSTAT: 0,,,IPV4\r\n", TRUE, FALSE, FALSE, FALSE }, + { "^NDISSTAT: 1,,,IPV6\r\n", FALSE, FALSE, TRUE, TRUE }, + { "^NDISSTAT: 0,,,IPV6\r\n", FALSE, FALSE, TRUE, FALSE }, + { "^NDISSTAT: 1,,,IPV4\r\n" + "^NDISSTAT: 1,,,IPV6\r\n", TRUE, TRUE, TRUE, TRUE }, + { "^NDISSTAT: 1,,,IPV4\r\n" + "^NDISSTAT: 0,,,IPV6\r\n", TRUE, TRUE, TRUE, FALSE }, + { "^NDISSTAT: 0,,,IPV4\r\n" + "^NDISSTAT: 1,,,IPV6\r\n", TRUE, FALSE, TRUE, TRUE }, + { "^NDISSTAT: 0,,,IPV4\r\n" + "^NDISSTAT: 0,,,IPV6\r\n", TRUE, FALSE, TRUE, FALSE }, + { "^NDISSTAT: 1,,,IPV4", TRUE, TRUE, FALSE, FALSE }, + { "^NDISSTAT: 0,,,IPV4", TRUE, FALSE, FALSE, FALSE }, + { "^NDISSTAT: 1,,,IPV6", FALSE, FALSE, TRUE, TRUE }, + { "^NDISSTAT: 0,,,IPV6", FALSE, FALSE, TRUE, FALSE }, + { "^NDISSTAT: 1,,,IPV4\r\n" + "^NDISSTAT: 1,,,IPV6", TRUE, TRUE, TRUE, TRUE }, + { "^NDISSTAT: 1,,,IPV4\r\n" + "^NDISSTAT: 0,,,IPV6", TRUE, TRUE, TRUE, FALSE }, + { "^NDISSTAT: 0,,,IPV4\r\n" + "^NDISSTAT: 1,,,IPV6", TRUE, FALSE, TRUE, TRUE }, + { "^NDISSTAT: 0,,,IPV4\r\n" + "^NDISSTAT: 0,,,IPV6", TRUE, FALSE, TRUE, FALSE }, + { "^NDISSTAT: 1,,,\"IPV4\",1,,,\"IPV6\"", TRUE, TRUE, TRUE, TRUE }, + { "^NDISSTAT: 1,,,\"IPV4\",0,,,\"IPV6\"", TRUE, TRUE, TRUE, FALSE }, + { "^NDISSTAT: 0,,,\"IPV4\",1,,,\"IPV6\"", TRUE, FALSE, TRUE, TRUE }, + { "^NDISSTAT: 0,,,\"IPV4\",0,,,\"IPV6\"", TRUE, FALSE, TRUE, FALSE }, + { "^NDISSTAT: 1,,,\"IPV4\",1,,,\"IPV6\"\r\n", TRUE, TRUE, TRUE, TRUE }, + { "^NDISSTAT: 1,,,\"IPV4\",0,,,\"IPV6\"\r\n", TRUE, TRUE, TRUE, FALSE }, + { "^NDISSTAT: 0,,,\"IPV4\",1,,,\"IPV6\"\r\n", TRUE, FALSE, TRUE, TRUE }, + { "^NDISSTAT: 0,,,\"IPV4\",0,,,\"IPV6\"\r\n", TRUE, FALSE, TRUE, FALSE }, + { "^NDISSTATQRY: 1,,,IPV4\r\n", TRUE, TRUE, FALSE, FALSE }, + { "^NDISSTATQRY: 0,,,IPV4\r\n", TRUE, FALSE, FALSE, FALSE }, + { "^NDISSTATQRY: 1,,,IPV6\r\n", FALSE, FALSE, TRUE, TRUE }, + { "^NDISSTATQRY: 0,,,IPV6\r\n", FALSE, FALSE, TRUE, FALSE }, + { "^NDISSTATQRY: 1,,,IPV4\r\n" + "^NDISSTATQRY: 1,,,IPV6\r\n", TRUE, TRUE, TRUE, TRUE }, + { "^NDISSTATQRY: 1,,,IPV4\r\n" + "^NDISSTATQRY: 0,,,IPV6\r\n", TRUE, TRUE, TRUE, FALSE }, + { "^NDISSTATQRY: 0,,,IPV4\r\n" + "^NDISSTATQRY: 1,,,IPV6\r\n", TRUE, FALSE, TRUE, TRUE }, + { "^NDISSTATQRY: 0,,,IPV4\r\n" + "^NDISSTATQRY: 0,,,IPV6\r\n", TRUE, FALSE, TRUE, FALSE }, + { "^NDISSTATQRY: 1,,,IPV4", TRUE, TRUE, FALSE, FALSE }, + { "^NDISSTATQRY: 0,,,IPV4", TRUE, FALSE, FALSE, FALSE }, + { "^NDISSTATQRY: 1,,,IPV6", FALSE, FALSE, TRUE, TRUE }, + { "^NDISSTATQRY: 0,,,IPV6", FALSE, FALSE, TRUE, FALSE }, + { "^NDISSTATQRY: 1,,,IPV4\r\n" + "^NDISSTATQRY: 1,,,IPV6", TRUE, TRUE, TRUE, TRUE }, + { "^NDISSTATQRY: 1,,,IPV4\r\n" + "^NDISSTATQRY: 0,,,IPV6", TRUE, TRUE, TRUE, FALSE }, + { "^NDISSTATQRY: 0,,,IPV4\r\n" + "^NDISSTATQRY: 1,,,IPV6", TRUE, FALSE, TRUE, TRUE }, + { "^NDISSTATQRY: 0,,,IPV4\r\n" + "^NDISSTATQRY: 0,,,IPV6", TRUE, FALSE, TRUE, FALSE }, + { "^NDISSTATQRY: 1,,,\"IPV4\",1,,,\"IPV6\"", TRUE, TRUE, TRUE, TRUE }, + { "^NDISSTATQRY: 1,,,\"IPV4\",0,,,\"IPV6\"", TRUE, TRUE, TRUE, FALSE }, + { "^NDISSTATQRY: 0,,,\"IPV4\",1,,,\"IPV6\"", TRUE, FALSE, TRUE, TRUE }, + { "^NDISSTATQRY: 0,,,\"IPV4\",0,,,\"IPV6\"", TRUE, FALSE, TRUE, FALSE }, + { "^NDISSTATQRY: 1,,,\"IPV4\",1,,,\"IPV6\"\r\n", TRUE, TRUE, TRUE, TRUE }, + { "^NDISSTATQRY: 1,,,\"IPV4\",0,,,\"IPV6\"\r\n", TRUE, TRUE, TRUE, FALSE }, + { "^NDISSTATQRY: 0,,,\"IPV4\",1,,,\"IPV6\"\r\n", TRUE, FALSE, TRUE, TRUE }, + { "^NDISSTATQRY: 0,,,\"IPV4\",0,,,\"IPV6\"\r\n", TRUE, FALSE, TRUE, FALSE }, + { "^NDISSTATQry:1", TRUE, TRUE, FALSE, FALSE }, + { "^NDISSTATQry:1\r\n", TRUE, TRUE, FALSE, FALSE }, + { "^NDISSTATQry:0", TRUE, FALSE, FALSE, FALSE }, + { "^NDISSTATQry:0\r\n", TRUE, FALSE, FALSE, FALSE }, + { NULL, FALSE, FALSE, FALSE, FALSE } +}; + +static void +test_ndisstatqry (void) +{ + guint i; + + for (i = 0; ndisstatqry_tests[i].str; i++) { + GError *error = NULL; + gboolean ipv4_available; + gboolean ipv4_connected; + gboolean ipv6_available; + gboolean ipv6_connected; + + g_assert (mm_huawei_parse_ndisstatqry_response ( + ndisstatqry_tests[i].str, + &ipv4_available, + &ipv4_connected, + &ipv6_available, + &ipv6_connected, + &error) == TRUE); + g_assert_no_error (error); + + g_assert (ipv4_available == ndisstatqry_tests[i].expected_ipv4_available); + if (ipv4_available) + g_assert (ipv4_connected == ndisstatqry_tests[i].expected_ipv4_connected); + g_assert (ipv6_available == ndisstatqry_tests[i].expected_ipv6_available); + if (ipv6_available) + g_assert (ipv6_connected == ndisstatqry_tests[i].expected_ipv6_connected); + } +} + +/*****************************************************************************/ +/* Test ^DHCP responses */ + +typedef struct { + const gchar *str; + const gchar *expected_addr; + guint expected_prefix; + const gchar *expected_gateway; + const gchar *expected_dns1; + const gchar *expected_dns2; +} DhcpTest; + +static const DhcpTest dhcp_tests[] = { + { "^DHCP:a3ec5c64,f8ffffff,a1ec5c64,a1ec5c64,2200b10a,74bba80a,236800,236800\r\n", + "100.92.236.163", 29, "100.92.236.161", "10.177.0.34", "10.168.187.116" }, + { "^DHCP:0xa3ec5c64,0xf8ffffff,0xa1ec5c64,0xa1ec5c64,0x2200b10a,0x74bba80a,236800,236800\r\n", + "100.92.236.163", 29, "100.92.236.161", "10.177.0.34", "10.168.187.116" }, + { "^DHCP: 1010A0A,FCFFFFFF,2010A0A,2010A0A,0,0,150000000,150000000\r\n", + "10.10.1.1", 30, "10.10.1.2", "0.0.0.0", "0.0.0.0" }, + { "^DHCP: CCDB080A,F8FFFFFF,C9DB080A,C9DB080A,E67B59C0,E77B59C0,85600,85600\r\n", + "10.8.219.204", 29, "10.8.219.201", "192.89.123.230", "192.89.123.231" }, + { "^DHCP: 0xCCDB080A,0xF8FFFFFF,0xC9DB080A,0xC9DB080A,0xE67B59C0,0xE77B59C0,85600,85600\r\n", + "10.8.219.204", 29, "10.8.219.201", "192.89.123.230", "192.89.123.231" }, + { "^DHCP: 0XCCDB080A,0XF8FFFFFF,0XC9DB080A,0XC9DB080A,0XE67B59C0,0XE77B59C0,85600,85600\r\n", + "10.8.219.204", 29, "10.8.219.201", "192.89.123.230", "192.89.123.231" }, + { NULL } +}; + +static void +test_dhcp (void) +{ + guint i; + + for (i = 0; dhcp_tests[i].str; i++) { + GError *error = NULL; + guint addr, prefix, gateway, dns1, dns2; + + g_assert (mm_huawei_parse_dhcp_response ( + dhcp_tests[i].str, + &addr, + &prefix, + &gateway, + &dns1, + &dns2, + &error) == TRUE); + g_assert_no_error (error); + + g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &addr)), ==, dhcp_tests[i].expected_addr); + g_assert_cmpint (prefix, ==, dhcp_tests[i].expected_prefix); + g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &gateway)), ==, dhcp_tests[i].expected_gateway); + g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &dns1)), ==, dhcp_tests[i].expected_dns1); + g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &dns2)), ==, dhcp_tests[i].expected_dns2); + } +} + +/*****************************************************************************/ +/* Test ^SYSINFO responses */ + +typedef struct { + const gchar *str; + guint expected_srv_status; + guint expected_srv_domain; + guint expected_roam_status; + guint expected_sys_mode; + guint expected_sim_state; + gboolean expected_sys_submode_valid; + guint expected_sys_submode; +} SysinfoTest; + +static const SysinfoTest sysinfo_tests[] = { + { "^SYSINFO:2,4,5,3,1", 2, 4, 5, 3, 1, FALSE, 0 }, + { "^SYSINFO:2,4,5,3,1,", 2, 4, 5, 3, 1, FALSE, 0 }, + { "^SYSINFO:2,4,5,3,1,,", 2, 4, 5, 3, 1, FALSE, 0 }, + { "^SYSINFO:2,4,5,3,1,6", 2, 4, 5, 3, 1, FALSE, 6 }, + { "^SYSINFO:2,4,5,3,1,6,", 2, 4, 5, 3, 1, FALSE, 6 }, + { "^SYSINFO:2,4,5,3,1,,6", 2, 4, 5, 3, 1, TRUE, 6 }, + { "^SYSINFO:2,4,5,3,1,0,6", 2, 4, 5, 3, 1, TRUE, 6 }, + { "^SYSINFO: 2,4,5,3,1,0,6", 2, 4, 5, 3, 1, TRUE, 6 }, + { NULL, 0, 0, 0, 0, 0, FALSE, 0 } +}; + +static void +test_sysinfo (void) +{ + guint i; + + for (i = 0; sysinfo_tests[i].str; i++) { + GError *error = NULL; + guint srv_status = 0; + guint srv_domain = 0; + guint roam_status = 0; + guint sys_mode = 0; + guint sim_state = 0; + gboolean sys_submode_valid = FALSE; + guint sys_submode = 0; + + g_assert (mm_huawei_parse_sysinfo_response (sysinfo_tests[i].str, + &srv_status, + &srv_domain, + &roam_status, + &sys_mode, + &sim_state, + &sys_submode_valid, + &sys_submode, + &error) == TRUE); + g_assert_no_error (error); + + g_assert (srv_status == sysinfo_tests[i].expected_srv_status); + g_assert (srv_domain == sysinfo_tests[i].expected_srv_domain); + g_assert (roam_status == sysinfo_tests[i].expected_roam_status); + g_assert (sys_mode == sysinfo_tests[i].expected_sys_mode); + g_assert (sim_state == sysinfo_tests[i].expected_sim_state); + g_assert (sys_submode_valid == sysinfo_tests[i].expected_sys_submode_valid); + if (sys_submode_valid) + g_assert (sys_submode == sysinfo_tests[i].expected_sys_submode); + } +} + +/*****************************************************************************/ +/* Test ^SYSINFOEX responses */ + +typedef struct { + const gchar *str; + guint expected_srv_status; + guint expected_srv_domain; + guint expected_roam_status; + guint expected_sim_state; + guint expected_sys_mode; + guint expected_sys_submode; +} SysinfoexTest; + +static const SysinfoexTest sysinfoex_tests[] = { + { "^SYSINFOEX:2,4,5,1,,3,WCDMA,41,HSPA+", 2, 4, 5, 1, 3, 41 }, + { "^SYSINFOEX:2,4,5,1,,3,\"WCDMA\",41,\"HSPA+\"", 2, 4, 5, 1, 3, 41 }, + { "^SYSINFOEX: 2,4,5,1,0,3,\"WCDMA\",41,\"HSPA+\"", 2, 4, 5, 1, 3, 41 }, + { NULL, 0, 0, 0, 0, 0, 0 } +}; + +static void +test_sysinfoex (void) +{ + guint i; + + for (i = 0; sysinfoex_tests[i].str; i++) { + GError *error = NULL; + guint srv_status = 0; + guint srv_domain = 0; + guint roam_status = 0; + guint sim_state = 0; + guint sys_mode = 0; + guint sys_submode = 0; + + g_assert (mm_huawei_parse_sysinfoex_response (sysinfoex_tests[i].str, + &srv_status, + &srv_domain, + &roam_status, + &sim_state, + &sys_mode, + &sys_submode, + &error) == TRUE); + g_assert_no_error (error); + + g_assert (srv_status == sysinfoex_tests[i].expected_srv_status); + g_assert (srv_domain == sysinfoex_tests[i].expected_srv_domain); + g_assert (roam_status == sysinfoex_tests[i].expected_roam_status); + g_assert (sim_state == sysinfoex_tests[i].expected_sim_state); + g_assert (sys_mode == sysinfoex_tests[i].expected_sys_mode); + g_assert (sys_submode == sysinfoex_tests[i].expected_sys_submode); + } +} + +/*****************************************************************************/ +/* Test ^PREFMODE=? responses */ + +#define MAX_PREFMODE_COMBINATIONS 3 + +typedef struct { + const gchar *str; + MMHuaweiPrefmodeCombination expected_modes[MAX_PREFMODE_COMBINATIONS]; +} PrefmodeTest; + +static const PrefmodeTest prefmode_tests[] = { + { + "^PREFMODE:(2,4,8)\r\n", + { + { + .prefmode = 8, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_NONE + }, + { + .prefmode = 4, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_3G + }, + { + .prefmode = 2, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_2G + } + } + }, + { + "^PREFMODE:(2,4)\r\n", + { + { + .prefmode = 4, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_3G + }, + { + .prefmode = 2, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_2G + }, + { 0, 0, 0} + } + }, + { + "^PREFMODE:(2)\r\n", + { + { + .prefmode = 2, + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE + }, + { 0, 0, 0} + } + }, +}; + +static void +test_prefmode (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (prefmode_tests); i++) { + GError *error = NULL; + GArray *combinations = NULL; + guint j; + guint n_expected_combinations = 0; + + for (j = 0; j < MAX_PREFMODE_COMBINATIONS; j++) { + if (prefmode_tests[i].expected_modes[j].prefmode != 0) + n_expected_combinations++; + } + + combinations = mm_huawei_parse_prefmode_test (prefmode_tests[i].str, NULL, &error); + g_assert_no_error (error); + g_assert (combinations != NULL); + g_assert_cmpuint (combinations->len, ==, n_expected_combinations); + + for (j = 0; j < combinations->len; j++) { + MMHuaweiPrefmodeCombination *single; + g_autofree gchar *allowed_str = NULL; + g_autofree gchar *preferred_str = NULL; + + single = &g_array_index (combinations, MMHuaweiPrefmodeCombination, j); + allowed_str = mm_modem_mode_build_string_from_mask (single->allowed); + preferred_str = mm_modem_mode_build_string_from_mask (single->preferred); + mm_obj_dbg (NULL, "test[%u], combination[%u]: %u, \"%s\", \"%s\"", + i, j, single->prefmode, allowed_str, preferred_str); + } + + for (j = 0; j < combinations->len; j++) { + MMHuaweiPrefmodeCombination *single; + guint k; + gboolean found = FALSE; + + single = &g_array_index (combinations, MMHuaweiPrefmodeCombination, j); + for (k = 0; k <= n_expected_combinations; k++) { + if (single->allowed == prefmode_tests[i].expected_modes[k].allowed && + single->preferred == prefmode_tests[i].expected_modes[k].preferred && + single->prefmode == prefmode_tests[i].expected_modes[k].prefmode) { + found = TRUE; + break; + } + } + + g_assert (found == TRUE); + } + + g_array_unref (combinations); + } +} + +/*****************************************************************************/ +/* Test ^PREFMODE? responses */ + +typedef struct { + const gchar *str; + const gchar *format; + MMModemMode allowed; + MMModemMode preferred; +} PrefmodeResponseTest; + +static const PrefmodeResponseTest prefmode_response_tests[] = { + { + .str = "^PREFMODE:2\r\n", + .format = "^PREFMODE:(2,4,8)\r\n", + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_2G + }, + { + .str = "^PREFMODE:4\r\n", + .format = "^PREFMODE:(2,4,8)\r\n", + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_3G + }, + { + .str = "^PREFMODE:8\r\n", + .format = "^PREFMODE:(2,4,8)\r\n", + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_NONE + } +}; + +static void +test_prefmode_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (prefmode_response_tests); i++) { + GArray *combinations = NULL; + const MMHuaweiPrefmodeCombination *found; + GError *error = NULL; + + combinations = mm_huawei_parse_prefmode_test (prefmode_response_tests[i].format, NULL, NULL); + g_assert (combinations != NULL); + + found = mm_huawei_parse_prefmode_response (prefmode_response_tests[i].str, + combinations, + &error); + g_assert_no_error (error); + g_assert (found != NULL); + g_assert_cmpuint (found->allowed, ==, prefmode_response_tests[i].allowed); + g_assert_cmpuint (found->preferred, ==, prefmode_response_tests[i].preferred); + + g_array_unref (combinations); + } +} + +/*****************************************************************************/ +/* Test ^SYSCFG=? responses */ + +#define MAX_SYSCFG_COMBINATIONS 5 + +typedef struct { + const gchar *str; + MMHuaweiSyscfgCombination expected_modes[MAX_SYSCFG_COMBINATIONS]; +} SyscfgTest; + +static const SyscfgTest syscfg_tests[] = { + { + MM_HUAWEI_DEFAULT_SYSCFG_FMT, + { + { + .mode = 2, + .acqorder = 0, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode = 2, + .acqorder = 1, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_2G + }, + { + .mode = 2, + .acqorder = 2, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_3G + }, + { + .mode = 13, + .acqorder = 0, + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode = 14, + .acqorder = 0, + .allowed = MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE + } + } + }, + { + "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n", + { + { + .mode = 2, + .acqorder = 0, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode = 2, + .acqorder = 1, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_2G + }, + { + .mode = 2, + .acqorder = 2, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_3G + }, + { + .mode = 13, + .acqorder = 0, + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode = 14, + .acqorder = 0, + .allowed = MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE + } + } + }, + { + "^SYSCFG:(2,13,14,16),(0),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n", + { + { + .mode = 2, + .acqorder = 0, + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode = 13, + .acqorder = 0, + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode = 14, + .acqorder = 0, + .allowed = MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE + }, + { 0, 0, 0, 0 } + } + }, + { + "^SYSCFG:(13),(0),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n", + { + { + .mode = 13, + .acqorder = 0, + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE + }, + { 0, 0, 0, 0 } + } + } +}; + +static void +test_syscfg (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (syscfg_tests); i++) { + GError *error = NULL; + GArray *combinations = NULL; + guint j; + guint n_expected_combinations = 0; + + for (j = 0; j < MAX_SYSCFG_COMBINATIONS; j++) { + if (syscfg_tests[i].expected_modes[j].mode != 0) + n_expected_combinations++; + } + + combinations = mm_huawei_parse_syscfg_test (syscfg_tests[i].str, NULL, &error); + g_assert_no_error (error); + g_assert (combinations != NULL); + g_assert_cmpuint (combinations->len, ==, n_expected_combinations); + + for (j = 0; j < combinations->len; j++) { + MMHuaweiSyscfgCombination *single; + g_autofree gchar *allowed_str = NULL; + g_autofree gchar *preferred_str = NULL; + + single = &g_array_index (combinations, MMHuaweiSyscfgCombination, j); + allowed_str = mm_modem_mode_build_string_from_mask (single->allowed); + preferred_str = mm_modem_mode_build_string_from_mask (single->preferred); + mm_obj_dbg (NULL, "test[%u], combination[%u]: %u, %u, \"%s\", \"%s\"", + i, j, single->mode, single->acqorder, allowed_str, preferred_str); + } + + for (j = 0; j < combinations->len; j++) { + MMHuaweiSyscfgCombination *single; + guint k; + gboolean found = FALSE; + + single = &g_array_index (combinations, MMHuaweiSyscfgCombination, j); + for (k = 0; k <= n_expected_combinations; k++) { + if (single->allowed == syscfg_tests[i].expected_modes[k].allowed && + single->preferred == syscfg_tests[i].expected_modes[k].preferred && + single->mode == syscfg_tests[i].expected_modes[k].mode && + single->acqorder == syscfg_tests[i].expected_modes[k].acqorder) { + found = TRUE; + break; + } + } + + g_assert (found == TRUE); + } + + g_array_unref (combinations); + } +} + +/*****************************************************************************/ +/* Test ^SYSCFG? responses */ + +typedef struct { + const gchar *str; + const gchar *format; + MMModemMode allowed; + MMModemMode preferred; +} SyscfgResponseTest; + +static const SyscfgResponseTest syscfg_response_tests[] = { + { + .str = "^SYSCFG: 2,0,400000,0,3\r\n", + .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n", + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_NONE + }, + { + .str = "^SYSCFG: 2,1,400000,0,3\r\n", + .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n", + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_2G + }, + { + .str = "^SYSCFG: 2,2,400000,0,3\r\n", + .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n", + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_3G + }, + { + .str = "^SYSCFG: 13,0,400000,0,3\r\n", + .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n", + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .str = "^SYSCFG: 14,0,400000,0,3\r\n", + .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n", + .allowed = MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE + }, + { + /* Non-sensical acquisition order (WCDMA-only but acquire WCDMA-then-GSM */ + .str = "^SYSCFG: 14,2,400000,0,3\r\n", + .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n", + .allowed = MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE + }, + { + /* Non-sensical acquisition order (GSM-only but acquire GSM-then-WCDMA */ + .str = "^SYSCFG: 13,1,400000,0,3\r\n", + .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n", + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE + } +}; + +static void +test_syscfg_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (syscfg_response_tests); i++) { + GArray *combinations = NULL; + const MMHuaweiSyscfgCombination *found; + GError *error = NULL; + + combinations = mm_huawei_parse_syscfg_test (syscfg_response_tests[i].format, NULL, NULL); + g_assert (combinations != NULL); + + found = mm_huawei_parse_syscfg_response (syscfg_response_tests[i].str, + combinations, + &error); + + g_assert_no_error (error); + g_assert (found != NULL); + g_assert_cmpuint (found->allowed, ==, syscfg_response_tests[i].allowed); + g_assert_cmpuint (found->preferred, ==, syscfg_response_tests[i].preferred); + + g_array_unref (combinations); + } +} + +/*****************************************************************************/ +/* Test ^SYSCFGEX=? responses */ + +#define MAX_SYSCFGEX_COMBINATIONS 5 + +typedef struct { + const gchar *str; + MMHuaweiSyscfgexCombination expected_modes[MAX_SYSCFGEX_COMBINATIONS]; +} SyscfgexTest; + +static const SyscfgexTest syscfgex_tests[] = { + { + "^SYSCFGEX: (\"00\",\"03\",\"02\",\"01\",\"99\")," + "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\"))," + "(0-3)," + "(0-4)," + "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))" + "\r\n", + { + { + .mode_str = (gchar *) "00", + .allowed = (MM_MODEM_MODE_4G | MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode_str = (gchar *) "03", + .allowed = MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode_str = (gchar *) "02", + .allowed = MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode_str = (gchar *) "01", + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE + }, + { NULL, 0, 0 } + } + }, + { + "^SYSCFGEX: (\"030201\",\"0302\",\"03\",\"99\")," + "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\"))," + "(0-3)," + "(0-4)," + "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))" + "\r\n", + { + { + .mode_str = (gchar *) "030201", + .allowed = (MM_MODEM_MODE_4G | MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_4G + }, + { + .mode_str = (gchar *) "0302", + .allowed = (MM_MODEM_MODE_4G | MM_MODEM_MODE_3G), + .preferred = MM_MODEM_MODE_4G + }, + { + .mode_str = (gchar *) "03", + .allowed = MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_NONE + }, + { NULL, 0, 0 } + } + }, + { + "^SYSCFGEX: (\"03\")," + "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\"))," + "(0-3)," + "(0-4)," + "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))" + "\r\n", + { + { + .mode_str = (gchar *) "03", + .allowed = MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_NONE + }, + { NULL, 0, 0 } + } + }, + { + "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\")," + "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\"))," + "(0-2)," + "(0-4)," + "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */ + "\r\n", + { + { + .mode_str = (gchar *) "00", + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode_str = (gchar *) "01", + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode_str = (gchar *) "02", + .allowed = MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .mode_str = (gchar *) "0102", + .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G), + .preferred = MM_MODEM_MODE_2G + }, + { + .mode_str = (gchar *) "0201", + .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G), + .preferred = MM_MODEM_MODE_3G + } + } + } +}; + +static void +test_syscfgex (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (syscfgex_tests); i++) { + GError *error = NULL; + GArray *combinations = NULL; + guint j; + guint n_expected_combinations = 0; + + for (j = 0; j < MAX_SYSCFGEX_COMBINATIONS; j++) { + if (syscfgex_tests[i].expected_modes[j].mode_str != NULL) + n_expected_combinations++; + } + + combinations = mm_huawei_parse_syscfgex_test (syscfgex_tests[i].str, &error); + g_assert_no_error (error); + g_assert (combinations != NULL); + g_assert_cmpuint (combinations->len, ==, n_expected_combinations); + + for (j = 0; j < combinations->len; j++) { + MMHuaweiSyscfgexCombination *single; + g_autofree gchar *allowed_str = NULL; + g_autofree gchar *preferred_str = NULL; + + single = &g_array_index (combinations, MMHuaweiSyscfgexCombination, j); + allowed_str = mm_modem_mode_build_string_from_mask (single->allowed); + preferred_str = mm_modem_mode_build_string_from_mask (single->preferred); + mm_obj_dbg (NULL, "test[%u], combination[%u]: \"%s\", \"%s\", \"%s\"", + i, j, single->mode_str, allowed_str, preferred_str); + } + + for (j = 0; j < combinations->len; j++) { + MMHuaweiSyscfgexCombination *single; + guint k; + gboolean found = FALSE; + + single = &g_array_index (combinations, MMHuaweiSyscfgexCombination, j); + for (k = 0; k <= n_expected_combinations; k++) { + if (g_str_equal (single->mode_str, syscfgex_tests[i].expected_modes[k].mode_str) && + single->allowed == syscfgex_tests[i].expected_modes[k].allowed && + single->preferred == syscfgex_tests[i].expected_modes[k].preferred) { + found = TRUE; + break; + } + } + + g_assert (found == TRUE); + } + + g_array_unref (combinations); + } +} + +/*****************************************************************************/ +/* Test ^SYSCFGEX? responses */ + +typedef struct { + const gchar *str; + const gchar *format; + MMModemMode allowed; + MMModemMode preferred; +} SyscfgexResponseTest; + +static const SyscfgexResponseTest syscfgex_response_tests[] = { + { + .str = "^SYSCFGEX: \"00\",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF", + .format = "^SYSCFGEX: (\"00\",\"03\",\"02\",\"01\",\"99\")," + "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\"))," + "(0-3)," + "(0-4)," + "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))" + "\r\n", + .allowed = (MM_MODEM_MODE_4G | MM_MODEM_MODE_3G | MM_MODEM_MODE_2G), + .preferred = MM_MODEM_MODE_NONE + }, + { + .str = "^SYSCFGEX: \"03\",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF", + .format = "^SYSCFGEX: (\"00\",\"03\",\"02\",\"01\",\"99\")," + "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\"))," + "(0-3)," + "(0-4)," + "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))" + "\r\n", + .allowed = MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .str = "^SYSCFGEX: \"02\",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF", + .format = "^SYSCFGEX: (\"00\",\"03\",\"02\",\"01\",\"99\")," + "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\"))," + "(0-3)," + "(0-4)," + "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))" + "\r\n", + .allowed = MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .str = "^SYSCFGEX: \"01\",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF", + .format = "^SYSCFGEX: (\"00\",\"03\",\"02\",\"01\",\"99\")," + "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\"))," + "(0-3)," + "(0-4)," + "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))" + "\r\n", + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .str = "^SYSCFGEX: \"00\",3fffffff,1,2,", + .format = "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\")," + "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\"))," + "(0-2)," + "(0-4)," + "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */ + "\r\n", + .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G), + .preferred = MM_MODEM_MODE_NONE + }, + { + .str = "^SYSCFGEX: \"01\",3fffffff,1,2,", + .format = "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\")," + "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\"))," + "(0-2)," + "(0-4)," + "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */ + "\r\n", + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .str = "^SYSCFGEX: \"02\",3fffffff,1,2,", + .format = "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\")," + "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\"))," + "(0-2)," + "(0-4)," + "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */ + "\r\n", + .allowed = MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE + }, + { + .str = "^SYSCFGEX: \"0102\",3fffffff,1,2,", + .format = "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\")," + "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\"))," + "(0-2)," + "(0-4)," + "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */ + "\r\n", + .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G), + .preferred = MM_MODEM_MODE_2G + }, + { + .str = "^SYSCFGEX: \"0201\",3fffffff,1,2,", + .format = "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\")," + "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\"))," + "(0-2)," + "(0-4)," + "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */ + "\r\n", + .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G), + .preferred = MM_MODEM_MODE_3G + } +}; + +static void +test_syscfgex_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (syscfgex_response_tests); i++) { + GArray *combinations = NULL; + const MMHuaweiSyscfgexCombination *found; + GError *error = NULL; + + combinations = mm_huawei_parse_syscfgex_test (syscfgex_response_tests[i].format, NULL); + g_assert (combinations != NULL); + + found = mm_huawei_parse_syscfgex_response (syscfgex_response_tests[i].str, + combinations, + &error); + + g_assert_no_error (error); + g_assert (found != NULL); + g_assert_cmpuint (found->allowed, ==, syscfgex_response_tests[i].allowed); + g_assert_cmpuint (found->preferred, ==, syscfgex_response_tests[i].preferred); + + g_array_unref (combinations); + } +} + +/*****************************************************************************/ +/* Test ^NWTIME responses */ + +typedef struct { + const gchar *str; + gboolean ret; + gboolean test_iso8601; + gboolean test_tz; + const gchar *iso8601; + gint32 offset; + gint32 dst_offset; + gint32 leap_seconds; +} NwtimeTest; + +#define NWT_UNKNOWN MM_NETWORK_TIMEZONE_LEAP_SECONDS_UNKNOWN + +static const NwtimeTest nwtime_tests[] = { + { "^NWTIME: 14/08/05,04:00:21+40,00", TRUE, TRUE, FALSE, + "2014-08-05T04:00:21+10", 600, 0, NWT_UNKNOWN }, + { "^NWTIME: 14/08/05,04:00:21+40,00", TRUE, FALSE, TRUE, + "2014-08-05T04:00:21+10", 600, 0, NWT_UNKNOWN }, + { "^NWTIME: 14/08/05,04:00:21+40,00", TRUE, TRUE, TRUE, + "2014-08-05T04:00:21+10", 600, 0, NWT_UNKNOWN }, + + { "^NWTIME: 14/08/05,04:00:21+20,00", TRUE, TRUE, FALSE, + "2014-08-05T04:00:21+05", 300, 0, NWT_UNKNOWN }, + { "^NWTIME: 14/08/05,04:00:21+20,00", TRUE, FALSE, TRUE, + "2014-08-05T04:00:21+05", 300, 0, NWT_UNKNOWN }, + { "^NWTIME: 14/08/05,04:00:21+20,00", TRUE, TRUE, TRUE, + "2014-08-05T04:00:21+05", 300, 0, NWT_UNKNOWN }, + + { "^NWTIME: 14/08/05,04:00:21+40,01", TRUE, TRUE, FALSE, + "2014-08-05T04:00:21+11", 600, 60, NWT_UNKNOWN }, + { "^NWTIME: 14/08/05,04:00:21+40,01", TRUE, FALSE, TRUE, + "2014-08-05T04:00:21+11", 600, 60, NWT_UNKNOWN }, + { "^NWTIME: 14/08/05,04:00:21+40,01", TRUE, TRUE, TRUE, + "2014-08-05T04:00:21+11", 600, 60, NWT_UNKNOWN }, + + { "^NWTIME: 14/08/05,04:00:21+40,02", TRUE, TRUE, FALSE, + "2014-08-05T04:00:21+12", 600, 120, NWT_UNKNOWN }, + { "^NWTIME: 14/08/05,04:00:21+40,02", TRUE, FALSE, TRUE, + "2014-08-05T04:00:21+12", 600, 120, NWT_UNKNOWN }, + { "^NWTIME: 14/08/05,04:00:21+40,02", TRUE, TRUE, TRUE, + "2014-08-05T04:00:21+12", 600, 120, NWT_UNKNOWN }, + + { "^TIME: XX/XX/XX,XX:XX:XX+XX,XX", FALSE, TRUE, FALSE, + NULL, NWT_UNKNOWN, NWT_UNKNOWN, NWT_UNKNOWN }, + + { "^TIME: 14/08/05,04:00:21+40,00", FALSE, TRUE, FALSE, + NULL, NWT_UNKNOWN, NWT_UNKNOWN, NWT_UNKNOWN }, + + { NULL, FALSE, FALSE, FALSE, NULL, NWT_UNKNOWN, NWT_UNKNOWN, NWT_UNKNOWN } +}; + +static void +test_nwtime (void) +{ + guint i; + + for (i = 0; nwtime_tests[i].str; i++) { + GError *error = NULL; + gchar *iso8601 = NULL; + MMNetworkTimezone *tz = NULL; + gboolean ret; + + ret = mm_huawei_parse_nwtime_response (nwtime_tests[i].str, + nwtime_tests[i].test_iso8601 ? &iso8601 : NULL, + nwtime_tests[i].test_tz ? &tz : NULL, + &error); + + g_assert (ret == nwtime_tests[i].ret); + g_assert (ret == (error ? FALSE : TRUE)); + + g_clear_error (&error); + + if (nwtime_tests[i].test_iso8601) + g_assert_cmpstr (nwtime_tests[i].iso8601, ==, iso8601); + + if (nwtime_tests[i].test_tz) { + g_assert (nwtime_tests[i].offset == mm_network_timezone_get_offset (tz)); + g_assert (nwtime_tests[i].dst_offset == mm_network_timezone_get_dst_offset (tz)); + g_assert (nwtime_tests[i].leap_seconds == mm_network_timezone_get_leap_seconds (tz)); + } + + g_free (iso8601); + + if (tz) + g_object_unref (tz); + } +} + +/*****************************************************************************/ +/* Test ^TIME responses */ + +typedef struct { + const gchar *str; + gboolean ret; + const gchar *iso8601; +} TimeTest; + +static const TimeTest time_tests[] = { + { "^TIME: 14/08/05 04:00:21", TRUE, "2014-08-05T04:00:21Z" }, + { "^TIME: 2014/08/05 04:00:21", TRUE, "2014-08-05T04:00:21Z" }, + { "^TIME: 14-08-05 04:00:21", FALSE, NULL }, + { "^TIME: 14-08-05,04:00:21", FALSE, NULL }, + { "^TIME: 14/08/05 04:00:21 AEST", FALSE, NULL }, + { NULL, FALSE, NULL } +}; + +static void +test_time (void) +{ + guint i; + + for (i = 0; time_tests[i].str; i++) { + GError *error = NULL; + gchar *iso8601 = NULL; + gboolean ret; + + ret = mm_huawei_parse_time_response (time_tests[i].str, + &iso8601, + NULL, + &error); + + g_assert (ret == time_tests[i].ret); + g_assert (ret == (error ? FALSE : TRUE)); + g_clear_error (&error); + + g_assert_cmpstr (time_tests[i].iso8601, ==, iso8601); + g_free (iso8601); + } +} + +/*****************************************************************************/ +/* Test ^HCSQ responses */ + +typedef struct { + const gchar *str; + gboolean ret; + MMModemAccessTechnology act; + guint value1; + guint value2; + guint value3; + guint value4; + guint value5; +} HcsqTest; + +static const HcsqTest hcsq_tests[] = { + { "^HCSQ:\"LTE\",30,19,66,0\r\n", TRUE, MM_MODEM_ACCESS_TECHNOLOGY_LTE, 30, 19, 66, 0, 0 }, + { "^HCSQ: \"WCDMA\",30,30,58\r\n", TRUE, MM_MODEM_ACCESS_TECHNOLOGY_UMTS, 30, 30, 58, 0, 0 }, + { "^HCSQ: \"GSM\",36,255\r\n", TRUE, MM_MODEM_ACCESS_TECHNOLOGY_GSM, 36, 255, 0, 0, 0 }, + { "^HCSQ: LTE,33,40,135,11\r\n", TRUE, MM_MODEM_ACCESS_TECHNOLOGY_LTE, 33, 40, 135, 11, 0 }, + { "^HCSQ: \"NOSERVICE\"\r\n", FALSE, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN, 0, 0, 0, 0, 0 }, + { "^HCSQ: NOSERVICE\r\n", FALSE, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN, 0, 0, 0, 0, 0 }, + { NULL, FALSE, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN, 0, 0, 0, 0, 0 } +}; + +static void +test_hcsq (void) +{ + guint i; + + for (i = 0; hcsq_tests[i].str; i++) { + GError *error = NULL; + MMModemAccessTechnology act; + guint value1 = 0; + guint value2 = 0; + guint value3 = 0; + guint value4 = 0; + guint value5 = 0; + gboolean ret; + + ret = mm_huawei_parse_hcsq_response (hcsq_tests[i].str, + &act, + &value1, + &value2, + &value3, + &value4, + &value5, + &error); + g_assert (ret == hcsq_tests[i].ret); + if (ret) { + g_assert_no_error (error); + g_assert_cmpint (hcsq_tests[i].act, ==, act); + g_assert_cmpint (hcsq_tests[i].value1, ==, value1); + g_assert_cmpint (hcsq_tests[i].value2, ==, value2); + g_assert_cmpint (hcsq_tests[i].value3, ==, value3); + g_assert_cmpint (hcsq_tests[i].value4, ==, value4); + g_assert_cmpint (hcsq_tests[i].value5, ==, value5); + } else + g_assert (error); + g_clear_error (&error); + } +} + +/*****************************************************************************/ +/* Test ^GETPORTMODE response */ + +typedef struct { + const gchar *str; + guint n_modes; + MMHuaweiPortMode modes[8]; +} GetportmodeTest; + +static const GetportmodeTest getportmode_tests[] = { + { + "^GETPORTMODE: TYPE: WCDMA: huawei,PCUI:0,MDM:1", + 2, { MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_MODEM } + }, + { + "^GETPORTMODE: TYPE: WCDMA: huawei,MDM:0,PCUI:1,NDIS:2,CDROM:3,SD:4,", + 5, { MM_HUAWEI_PORT_MODE_MODEM, + MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_NET, + MM_HUAWEI_PORT_MODE_CDROM, + MM_HUAWEI_PORT_MODE_SD } + }, + { + "^GETPORTMODE: TYPE: WCDMA: huawei,MDM:0,PCUI:1,NDIS:2,GPS:3,BT:4,", + 5, { MM_HUAWEI_PORT_MODE_MODEM, + MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_NET, + MM_HUAWEI_PORT_MODE_GPS, + MM_HUAWEI_PORT_MODE_BT + } + }, + { + "^GETPORTMODE: TYPE: WCDMA: huawei,PCUI:0,MDM:1,NDIS:2,CDROM:3,SD:4,GPS:5,BT:6", + 7, { MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_MODEM, + MM_HUAWEI_PORT_MODE_NET, + MM_HUAWEI_PORT_MODE_CDROM, + MM_HUAWEI_PORT_MODE_SD, + MM_HUAWEI_PORT_MODE_GPS, + MM_HUAWEI_PORT_MODE_BT + } + }, + { + "^getportmode:type:WCDMA:Qualcomm,NDIS:0,DIAG:1,PCUI:2,MDM:3,SD:4", + 5, { MM_HUAWEI_PORT_MODE_NET, + MM_HUAWEI_PORT_MODE_DIAG, + MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_MODEM, + MM_HUAWEI_PORT_MODE_SD + } + }, + { + "^GETPORTMODE: TYPE: WCDMA: ,pcui:1,modem:2,ncm:3,mass:4,mass_two:5,", + 5, { MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_MODEM, + MM_HUAWEI_PORT_MODE_NET, + MM_HUAWEI_PORT_MODE_SD, + MM_HUAWEI_PORT_MODE_SD + } + }, + { + "^GETPORTMODE: TYPE: WCDMA: huawei ,, rndis: 0, pcui: 1, c_shell: 2, a_shell: 3,3g_diag: 4, gps: 5, 4g_diag: 6, mass_two: 7", + 8, { MM_HUAWEI_PORT_MODE_NET, + MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_SHELL, + MM_HUAWEI_PORT_MODE_SHELL, + MM_HUAWEI_PORT_MODE_DIAG, + MM_HUAWEI_PORT_MODE_GPS, + MM_HUAWEI_PORT_MODE_DIAG, + MM_HUAWEI_PORT_MODE_SD + } + }, + { + "^GETPORTMODE: TYPE: WCDMA: huawei,ecm:1,pcui:2,c_shell:3,a_shell:4,3g_diag:5,gps:6,4g_diag:7,mass:8,", + 8, { MM_HUAWEI_PORT_MODE_NET, + MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_SHELL, + MM_HUAWEI_PORT_MODE_SHELL, + MM_HUAWEI_PORT_MODE_DIAG, + MM_HUAWEI_PORT_MODE_GPS, + MM_HUAWEI_PORT_MODE_DIAG, + MM_HUAWEI_PORT_MODE_SD + } + }, + { + "^GETPORTMODE: TYPE: WCDMA: huawei,rndis:1,pcui:2,c_shell:3,a_shell:4,3g_diag:5,gps:6,4g_diag:7,mass:8,", + 8, { MM_HUAWEI_PORT_MODE_NET, + MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_SHELL, + MM_HUAWEI_PORT_MODE_SHELL, + MM_HUAWEI_PORT_MODE_DIAG, + MM_HUAWEI_PORT_MODE_GPS, + MM_HUAWEI_PORT_MODE_DIAG, + MM_HUAWEI_PORT_MODE_SD + } + }, + { + "^GETPORTMODE: TYPE: WCDMA: huawei,,pcui:0,3g_modem:1,ncm:2,mass:3,mass_two:4", + 5, { MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_MODEM, + MM_HUAWEI_PORT_MODE_NET, + MM_HUAWEI_PORT_MODE_SD, + MM_HUAWEI_PORT_MODE_SD + } + }, + { + "^GETPORTMODE:TYPE:WCDMA:Qualcomm,MDM:0,NDIS:1,DIAG:2,PCUI:3,CDROM:4,SD:5", + 6, { MM_HUAWEI_PORT_MODE_MODEM, + MM_HUAWEI_PORT_MODE_NET, + MM_HUAWEI_PORT_MODE_DIAG, + MM_HUAWEI_PORT_MODE_PCUI, + MM_HUAWEI_PORT_MODE_CDROM, + MM_HUAWEI_PORT_MODE_SD + } + }, + { + "^GETPORTMODE: TYPE: WCDMA: Huawei Technologies Co.,Ltd.,", + 0 + }, +}; + +static void +test_getportmode (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (getportmode_tests); i++) { + g_autoptr(GArray) modes = NULL; + g_autoptr(GError) error = NULL; + + mm_obj_dbg (NULL, "testing ^GETPORTMODE response: '%s'", getportmode_tests[i].str); + + modes = mm_huawei_parse_getportmode_response (getportmode_tests[i].str, NULL, &error); + if (modes) { + guint j; + + g_assert_no_error (error); + g_assert_cmpuint (modes->len, ==, getportmode_tests[i].n_modes); + for (j = 0; j < getportmode_tests[i].n_modes; j++) + g_assert_cmpuint (g_array_index (modes, MMHuaweiPortMode, j), ==, getportmode_tests[i].modes[j]); + } else + g_assert (error); + } +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/huawei/ndisstatqry", test_ndisstatqry); + g_test_add_func ("/MM/huawei/dhcp", test_dhcp); + g_test_add_func ("/MM/huawei/sysinfo", test_sysinfo); + g_test_add_func ("/MM/huawei/sysinfoex", test_sysinfoex); + g_test_add_func ("/MM/huawei/prefmode", test_prefmode); + g_test_add_func ("/MM/huawei/prefmode/response", test_prefmode_response); + g_test_add_func ("/MM/huawei/syscfg", test_syscfg); + g_test_add_func ("/MM/huawei/syscfg/response", test_syscfg_response); + g_test_add_func ("/MM/huawei/syscfgex", test_syscfgex); + g_test_add_func ("/MM/huawei/syscfgex/response", test_syscfgex_response); + g_test_add_func ("/MM/huawei/nwtime", test_nwtime); + g_test_add_func ("/MM/huawei/time", test_time); + g_test_add_func ("/MM/huawei/hcsq", test_hcsq); + g_test_add_func ("/MM/huawei/getportmode", test_getportmode); + + return g_test_run (); +} diff --git a/src/plugins/icera/mm-broadband-bearer-icera.c b/src/plugins/icera/mm-broadband-bearer-icera.c new file mode 100644 index 00000000..f6477595 --- /dev/null +++ b/src/plugins/icera/mm-broadband-bearer-icera.c @@ -0,0 +1,882 @@ +/* -*- 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) 2012 Google, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-bearer-icera.h" +#include "mm-base-modem-at.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-error-helpers.h" +#include "mm-daemon-enums-types.h" +#include "mm-modem-helpers-icera.h" + +G_DEFINE_TYPE (MMBroadbandBearerIcera, mm_broadband_bearer_icera, MM_TYPE_BROADBAND_BEARER); + +enum { + PROP_0, + PROP_DEFAULT_IP_METHOD, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +struct _MMBroadbandBearerIceraPrivate { + MMBearerIpMethod default_ip_method; + + /* Connection related */ + gpointer connect_pending; + guint connect_pending_id; + gulong connect_cancellable_id; + gulong connect_port_closed_id; + + /* Disconnection related */ + gpointer disconnect_pending; + guint disconnect_pending_id; +}; + +/*****************************************************************************/ +/* 3GPP IP config retrieval (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + guint cid; +} GetIpConfig3gppContext; + +static void +get_ip_config_context_free (GetIpConfig3gppContext *ctx) +{ + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_free (ctx); +} + +static gboolean +get_ip_config_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + MMBearerIpConfig **ipv4_config, + MMBearerIpConfig **ipv6_config, + GError **error) +{ + MMBearerConnectResult *configs; + MMBearerIpConfig *ipv4, *ipv6; + + configs = g_task_propagate_pointer (G_TASK (res), error); + if (!configs) + return FALSE; + + ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs); + ipv6 = mm_bearer_connect_result_peek_ipv6_config (configs); + g_assert (ipv4 || ipv6); + if (ipv4_config && ipv4) + *ipv4_config = g_object_ref (ipv4); + if (ipv6_config && ipv6) + *ipv6_config = g_object_ref (ipv6); + + mm_bearer_connect_result_unref (configs); + return TRUE; +} + +static void +ip_config_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GetIpConfig3gppContext *ctx; + MMBearerIpConfig *ipv4_config = NULL; + MMBearerIpConfig *ipv6_config = NULL; + const gchar *response; + GError *error = NULL; + MMBearerConnectResult *connect_result; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + g_task_return_error (task, error); + goto out; + } + + if (!mm_icera_parse_ipdpaddr_response (response, + ctx->cid, + &ipv4_config, + &ipv6_config, + &error)) { + g_task_return_error (task, error); + goto out; + } + + if (!ipv4_config && !ipv6_config) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get IP config: couldn't parse response '%s'", + response); + goto out; + } + + connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->primary), + ipv4_config, + ipv6_config); + g_task_return_pointer (task, + connect_result, + (GDestroyNotify)mm_bearer_connect_result_unref); + +out: + g_object_unref (task); + g_clear_object (&ipv4_config); + g_clear_object (&ipv6_config); +} + +static void +get_ip_config_3gpp (MMBroadbandBearer *_self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + MMBearerIpFamily ip_family, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (_self); + GetIpConfig3gppContext *ctx; + GTask *task; + + ctx = g_new0 (GetIpConfig3gppContext, 1); + ctx->modem = g_object_ref (MM_BASE_MODEM (modem)); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)get_ip_config_context_free); + + if (self->priv->default_ip_method == MM_BEARER_IP_METHOD_STATIC) { + gchar *command; + + command = g_strdup_printf ("%%IPDPADDR=%u", cid); + mm_base_modem_at_command_full (MM_BASE_MODEM (modem), + primary, + command, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)ip_config_ready, + task); + g_free (command); + return; + } + + /* Otherwise, DHCP */ + if (self->priv->default_ip_method == MM_BEARER_IP_METHOD_DHCP) { + MMBearerConnectResult *connect_result; + MMBearerIpConfig *ipv4_config = NULL, *ipv6_config = NULL; + + if (ip_family & MM_BEARER_IP_FAMILY_IPV4 || ip_family & MM_BEARER_IP_FAMILY_IPV4V6) { + ipv4_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP); + } + if (ip_family & MM_BEARER_IP_FAMILY_IPV6 || ip_family & MM_BEARER_IP_FAMILY_IPV4V6) { + ipv6_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); + } + g_assert (ipv4_config || ipv6_config); + + connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->primary), + ipv4_config, + ipv6_config); + g_clear_object (&ipv4_config); + g_clear_object (&ipv6_config); + + g_task_return_pointer (task, + connect_result, + (GDestroyNotify)mm_bearer_connect_result_unref); + g_object_unref (task); + return; + } + + g_assert_not_reached (); +} + +/*****************************************************************************/ +/* 3GPP disconnection */ + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +disconnect_3gpp_timed_out_cb (MMBroadbandBearerIcera *self) +{ + GTask *task; + + /* Recover disconnection task */ + task = self->priv->disconnect_pending; + + self->priv->disconnect_pending = NULL; + self->priv->disconnect_pending_id = 0; + + g_task_return_new_error (task, + MM_SERIAL_ERROR, + MM_SERIAL_ERROR_RESPONSE_TIMEOUT, + "Disconnection attempt timed out"); + g_object_unref (task); + + return G_SOURCE_REMOVE; +} + +static void +process_pending_disconnect_attempt (MMBroadbandBearerIcera *self, + MMBearerConnectionStatus status) +{ + GTask *task; + + /* Recover disconnection task */ + task = self->priv->disconnect_pending; + self->priv->disconnect_pending = NULL; + g_assert (task != NULL); + + /* Cleanup timeout, if any */ + if (self->priv->disconnect_pending_id) { + g_source_remove (self->priv->disconnect_pending_id); + self->priv->disconnect_pending_id = 0; + } + + /* Received 'CONNECTED' during a disconnection attempt? */ + if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Disconnection failed"); + g_object_unref (task); + return; + } + + /* Received 'DISCONNECTED' during a disconnection attempt? */ + if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED || + status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* No other status is expected by this implementation */ + g_assert_not_reached (); +} + +static void +disconnect_ipdpact_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerIcera *self) +{ + GError *error = NULL; + GTask *task; + + /* Try to recover the disconnection task. If none found, it means the + * task was already completed and we have nothing else to do. */ + task = g_steal_pointer (&self->priv->disconnect_pending); + + if (!task) { + mm_obj_dbg (self, "disconnection context was finished already by an unsolicited message"); + /* Run _finish() to finalize the async call, even if we don't care + * about the result */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + goto out; + } + + mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + goto out; + } + + /* Track again */ + self->priv->disconnect_pending = task; + + /* Set a 60-second disconnection-failure timeout */ + self->priv->disconnect_pending_id = g_timeout_add_seconds (60, + (GSourceFunc)disconnect_3gpp_timed_out_cb, + self); + +out: + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); +} + +static void +disconnect_3gpp (MMBroadbandBearer *bearer, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (bearer); + gchar *command; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* The unsolicited response to %IPDPACT may come before the OK does. + * We will keep the disconnection task in the bearer private data so + * that it is accessible from the unsolicited message handler. Note + * also that we do NOT pass the task to the GAsyncReadyCallback, as it + * may not be valid any more when the callback is called (it may be + * already completed in the unsolicited handling) */ + g_assert (self->priv->disconnect_pending == NULL); + self->priv->disconnect_pending = task; + + command = g_strdup_printf ("%%IPDPACT=%d,0", cid); + mm_base_modem_at_command_full ( + MM_BASE_MODEM (modem), + primary, + command, + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)disconnect_ipdpact_ready, + g_object_ref (self)); /* we pass the bearer object! */ + g_free (command); +} + +/*****************************************************************************/ +/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + guint cid; + MMPort *data; + guint authentication_retries; + GError *saved_error; +} Dial3gppContext; + +static void +dial_3gpp_context_free (Dial3gppContext *ctx) +{ + g_assert (!ctx->saved_error); + g_clear_object (&ctx->data); + g_clear_object (&ctx->primary); + g_clear_object (&ctx->modem); + g_slice_free (Dial3gppContext, ctx); +} + +static guint +dial_3gpp_get_connecting_cid (GTask *task) +{ + Dial3gppContext *ctx; + + ctx = g_task_get_task_data (task); + return ctx->cid; +} + +static MMPort * +dial_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return MM_PORT (g_task_propagate_pointer (G_TASK (res), error)); +} + +static void +connect_reset_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + Dial3gppContext *ctx; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_full_finish (modem, res, NULL); + + /* When reset is requested, it was either cancelled or an error was stored */ + if (!g_task_return_error_if_cancelled (task)) { + g_assert (ctx->saved_error); + g_task_return_error (task, ctx->saved_error); + ctx->saved_error = NULL; + } + + g_object_unref (task); +} + +static void +connect_reset (GTask *task) +{ + Dial3gppContext *ctx; + gchar *command; + + ctx = g_task_get_task_data (task); + + /* Need to reset the connection attempt */ + command = g_strdup_printf ("%%IPDPACT=%d,0", ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)connect_reset_ready, + task); + g_free (command); +} + +static gboolean +connect_timed_out_cb (MMBroadbandBearerIcera *self) +{ + GTask *task; + Dial3gppContext *ctx; + + /* Cleanup timeout ID */ + self->priv->connect_pending_id = 0; + + /* Recover task and own it */ + task = self->priv->connect_pending; + self->priv->connect_pending = NULL; + g_assert (task); + + ctx = g_task_get_task_data (task); + + /* Remove closed port watch, if found */ + if (self->priv->connect_port_closed_id) { + g_signal_handler_disconnect (ctx->primary, self->priv->connect_port_closed_id); + self->priv->connect_port_closed_id = 0; + } + + /* Setup error to return after the reset */ + g_assert (!ctx->saved_error); + ctx->saved_error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, + "Connection attempt timed out"); + + /* It's probably pointless to try to reset this here, but anyway... */ + connect_reset (task); + + return G_SOURCE_REMOVE; +} + +static void +ier_query_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerIcera *self; + const gchar *response; + GError *activation_error = NULL; + + self = g_task_get_source_object (task); + + response = mm_base_modem_at_command_full_finish (modem, res, NULL); + if (response) { + gint nw_activation_err; + + response = mm_strip_tag (response, "%IER:"); + if (sscanf (response, "%*d,%*d,%d", &nw_activation_err)) { + /* 3GPP TS 24.008 Annex G error codes: + * 27 - Unknown or missing access point name + * 33 - Requested service option not subscribed + */ + if (nw_activation_err == 27 || nw_activation_err == 33) + activation_error = mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_SERVICE_OPTION_NOT_SUBSCRIBED, self); + } + } + + if (activation_error) + g_task_return_error (task, activation_error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Call setup failed"); + g_object_unref (task); +} + +static void +process_pending_connect_attempt (MMBroadbandBearerIcera *self, + MMBearerConnectionStatus status) +{ + GTask *task; + Dial3gppContext *ctx; + + /* Recover task and remove both cancellation and timeout (if any)*/ + g_assert (self->priv->connect_pending); + task = self->priv->connect_pending; + self->priv->connect_pending = NULL; + + ctx = g_task_get_task_data (task); + + if (self->priv->connect_pending_id) { + g_source_remove (self->priv->connect_pending_id); + self->priv->connect_pending_id = 0; + } + + if (self->priv->connect_port_closed_id) { + g_signal_handler_disconnect (ctx->primary, self->priv->connect_port_closed_id); + self->priv->connect_port_closed_id = 0; + } + + /* Received 'CONNECTED' during a connection attempt? */ + if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED) { + /* If we wanted to get cancelled before, do it now. */ + if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) { + connect_reset (task); + return; + } + + g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); + g_object_unref (task); + return; + } + + /* If we wanted to get cancelled before and now we couldn't connect, + * use the cancelled error and return */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + /* Received CONNECTION_FAILED during a connection attempt? */ + if (status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED) { + /* Try to gather additional info about the connection failure */ + mm_base_modem_at_command_full ( + ctx->modem, + ctx->primary, + "%IER?", + 60, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback) ier_query_ready, + task); + return; + } + + /* Otherwise, received 'DISCONNECTED' during a connection attempt? */ + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Call setup failed"); + g_object_unref (task); +} + +static void +forced_close_cb (MMBroadbandBearerIcera *self) +{ + /* Just treat the forced close event as any other unsolicited message */ + mm_base_bearer_report_connection_status (MM_BASE_BEARER (self), + MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED); +} + +static void +activate_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerIcera *self) +{ + GTask *task; + Dial3gppContext *ctx; + GError *error = NULL; + + task = g_steal_pointer (&self->priv->connect_pending); + + /* Try to recover the connection context. If none found, it means the + * context was already completed and we have nothing else to do. */ + if (!task) { + mm_obj_dbg (self, "connection context was finished already by an unsolicited message"); + /* Run _finish() to finalize the async call, even if we don't care + * the result */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + goto out; + } + + /* Errors on the dial command are fatal */ + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + goto out; + } + + /* Track again */ + self->priv->connect_pending = task; + + /* We will now setup a timeout and keep the context in the bearer's private. + * Reports of modem being connected will arrive via unsolicited messages. + * This timeout should be long enough. Actually... ideally should never get + * reached. */ + self->priv->connect_pending_id = g_timeout_add_seconds (MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + (GSourceFunc)connect_timed_out_cb, + self); + + /* If we get the port closed, we treat as a connect error */ + ctx = g_task_get_task_data (task); + self->priv->connect_port_closed_id = g_signal_connect_swapped (ctx->primary, + "forced-close", + G_CALLBACK (forced_close_cb), + self); + + out: + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); +} + +static void +dial_3gpp (MMBroadbandBearer *_self, + MMBaseModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (_self); + GTask *task; + Dial3gppContext *ctx; + g_autofree gchar *cmd = NULL; + + g_assert (primary != NULL); + + task = g_task_new (self, cancellable, callback, user_data); + + ctx = g_slice_new0 (Dial3gppContext); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + g_task_set_task_data (task, ctx, (GDestroyNotify)dial_3gpp_context_free); + + /* We need a net data port */ + ctx->data = mm_base_modem_get_best_data_port (modem, MM_PORT_TYPE_NET); + if (!ctx->data) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to launch connection"); + g_object_unref (task); + return; + } + + /* The unsolicited response to %IPDPACT may come before the OK does. + * We will keep the connection context in the bearer private data so + * that it is accessible from the unsolicited message handler. Note + * also that we do NOT pass the ctx to the GAsyncReadyCallback, as it + * may not be valid any more when the callback is called (it may be + * already completed in the unsolicited handling) */ + g_assert (self->priv->connect_pending == NULL); + self->priv->connect_pending = task; + + cmd = g_strdup_printf ("%%IPDPACT=%d,1", ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + cmd, + MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback) activate_ready, + g_object_ref (self)); /* we pass the bearer object! */ +} + +/*****************************************************************************/ + +gint +mm_broadband_bearer_icera_get_connecting_profile_id (MMBroadbandBearerIcera *self) +{ + return (self->priv->connect_pending ? + (gint)dial_3gpp_get_connecting_cid (self->priv->connect_pending) : + MM_3GPP_PROFILE_ID_UNKNOWN); +} + +/*****************************************************************************/ + +static void +report_connection_status (MMBaseBearer *_self, + MMBearerConnectionStatus status, + const GError *connection_error) +{ + MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (_self); + + g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED || + status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED || + status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED); + + /* Process pending connection attempt */ + if (self->priv->connect_pending) { + process_pending_connect_attempt (self, status); + return; + } + + /* Process pending disconnection attempt */ + if (self->priv->disconnect_pending) { + process_pending_disconnect_attempt (self, status); + return; + } + + mm_obj_dbg (self, "received spontaneous %%IPDPACT (%s)", mm_bearer_connection_status_get_string (status)); + + /* Received a random 'DISCONNECTED'...*/ + if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED || + status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED) { + /* If no connection/disconnection attempt on-going, make sure we mark ourselves as + * disconnected. Make sure we only pass 'DISCONNECTED' to the parent */ + MM_BASE_BEARER_CLASS (mm_broadband_bearer_icera_parent_class)->report_connection_status ( + _self, + MM_BEARER_CONNECTION_STATUS_DISCONNECTED, + connection_error); + } +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_broadband_bearer_icera_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *bearer; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_icera_new (MMBroadbandModem *modem, + MMBearerIpMethod ip_method, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_ICERA, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + MM_BROADBAND_BEARER_ICERA_DEFAULT_IP_METHOD, ip_method, + NULL); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (object); + + switch (prop_id) { + case PROP_DEFAULT_IP_METHOD: + self->priv->default_ip_method = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (object); + + switch (prop_id) { + case PROP_DEFAULT_IP_METHOD: + g_value_set_enum (value, self->priv->default_ip_method); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +mm_broadband_bearer_icera_init (MMBroadbandBearerIcera *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_BEARER_ICERA, + MMBroadbandBearerIceraPrivate); + + /* Defaults */ + self->priv->default_ip_method = MM_BEARER_IP_METHOD_STATIC; +} + +static void +mm_broadband_bearer_icera_class_init (MMBroadbandBearerIceraClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandBearerIceraPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + base_bearer_class->report_connection_status = report_connection_status; + base_bearer_class->load_connection_status = NULL; + base_bearer_class->load_connection_status_finish = NULL; +#if defined WITH_SUSPEND_RESUME + base_bearer_class->reload_connection_status = NULL; + base_bearer_class->reload_connection_status_finish = NULL; +#endif + + broadband_bearer_class->dial_3gpp = dial_3gpp; + broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; + broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp; + broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; + + properties[PROP_DEFAULT_IP_METHOD] = + g_param_spec_enum (MM_BROADBAND_BEARER_ICERA_DEFAULT_IP_METHOD, + "Default IP method", + "Default IP Method (static or DHCP) to use.", + MM_TYPE_BEARER_IP_METHOD, + MM_BEARER_IP_METHOD_STATIC, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_DEFAULT_IP_METHOD, properties[PROP_DEFAULT_IP_METHOD]); +} diff --git a/src/plugins/icera/mm-broadband-bearer-icera.h b/src/plugins/icera/mm-broadband-bearer-icera.h new file mode 100644 index 00000000..e169cb7c --- /dev/null +++ b/src/plugins/icera/mm-broadband-bearer-icera.h @@ -0,0 +1,66 @@ +/* -*- 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: + * + * Author: Nathan Williams <njw@google.com> + * + * Copyright (C) 2012 Google, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_BEARER_ICERA_H +#define MM_BROADBAND_BEARER_ICERA_H + +#include <glib.h> +#include <glib-object.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-bearer.h" + +#define MM_TYPE_BROADBAND_BEARER_ICERA (mm_broadband_bearer_icera_get_type ()) +#define MM_BROADBAND_BEARER_ICERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_ICERA, MMBroadbandBearerIcera)) +#define MM_BROADBAND_BEARER_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_ICERA, MMBroadbandBearerIceraClass)) +#define MM_IS_BROADBAND_BEARER_ICERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_ICERA)) +#define MM_IS_BROADBAND_BEARER_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_ICERA)) +#define MM_BROADBAND_BEARER_ICERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_ICERA, MMBroadbandBearerIceraClass)) + +#define MM_BROADBAND_BEARER_ICERA_DEFAULT_IP_METHOD "broadband-bearer-icera-default-ip-method" + +typedef struct _MMBroadbandBearerIcera MMBroadbandBearerIcera; +typedef struct _MMBroadbandBearerIceraClass MMBroadbandBearerIceraClass; +typedef struct _MMBroadbandBearerIceraPrivate MMBroadbandBearerIceraPrivate; + +struct _MMBroadbandBearerIcera { + MMBroadbandBearer parent; + MMBroadbandBearerIceraPrivate *priv; +}; + +struct _MMBroadbandBearerIceraClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_icera_get_type (void); + +/* Default bearer creation implementation */ +void mm_broadband_bearer_icera_new (MMBroadbandModem *modem, + MMBearerIpMethod ip_method, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_icera_new_finish (GAsyncResult *res, + GError **error); + +gint mm_broadband_bearer_icera_get_connecting_profile_id (MMBroadbandBearerIcera *self); + +#endif /* MM_BROADBAND_BEARER_ICERA_H */ diff --git a/src/plugins/icera/mm-broadband-modem-icera.c b/src/plugins/icera/mm-broadband-modem-icera.c new file mode 100644 index 00000000..759985e0 --- /dev/null +++ b/src/plugins/icera/mm-broadband-modem-icera.c @@ -0,0 +1,2374 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-serial-parsers.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-3gpp-profile-manager.h" +#include "mm-iface-modem-time.h" +#include "mm-common-helpers.h" +#include "mm-base-modem-at.h" +#include "mm-bearer-list.h" +#include "mm-broadband-bearer-icera.h" +#include "mm-broadband-modem-icera.h" +#include "mm-modem-helpers-icera.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManager *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModem3gppProfileManager *iface_modem_3gpp_profile_manager_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemIcera, mm_broadband_modem_icera, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_PROFILE_MANAGER, iface_modem_3gpp_profile_manager_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)) + +enum { + PROP_0, + PROP_DEFAULT_IP_METHOD, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +struct _MMBroadbandModemIceraPrivate { + MMBearerIpMethod default_ip_method; + + GRegex *nwstate_regex; + GRegex *pacsp_regex; + GRegex *ipdpact_regex; + + /* Cache of the most recent value seen by the unsolicited message handler */ + MMModemAccessTechnology last_act; +}; + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static void +add_supported_mode (MMBroadbandModemIcera *self, + GArray **combinations, + guint mode) +{ + MMModemModeCombination combination; + + switch (mode) { + case 0: + mm_obj_dbg (self, "2G-only mode supported"); + combination.allowed = MM_MODEM_MODE_2G; + combination.preferred = MM_MODEM_MODE_NONE; + break; + case 1: + mm_obj_dbg (self, "3G-only mode supported"); + combination.allowed = MM_MODEM_MODE_3G; + combination.preferred = MM_MODEM_MODE_NONE; + break; + case 2: + mm_obj_dbg (self, "2G/3G mode with 2G preferred supported"); + combination.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + combination.preferred = MM_MODEM_MODE_2G; + break; + case 3: + mm_obj_dbg (self, "2G/3G mode with 3G preferred supported"); + combination.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + combination.preferred = MM_MODEM_MODE_3G; + break; + case 5: + /* Any, no need to add it to the list */ + return; + default: + mm_obj_warn (self, "unsupported mode found in %%IPSYS=?: %u", mode); + return; + } + + if (*combinations == NULL) + *combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5); + + g_array_append_val (*combinations, combination); +} + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + GArray *combinations = NULL; + const gchar *response; + gchar **split = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GRegex) r = NULL; + guint i; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return NULL; + + /* Reply goes like this: + * AT%IPSYS=? + * %IPSYS: (0-3,5),(0-3) + */ + + r = g_regex_new ("\\%IPSYS:\\s*\\((.*)\\)\\s*,\\((.*)\\)", + G_REGEX_RAW, 0, NULL); + g_assert (r != NULL); + + g_regex_match (r, response, 0, &match_info); + if (g_match_info_matches (match_info)) { + g_autofree gchar *aux = NULL; + + aux = mm_get_string_unquoted_from_match_info (match_info, 1); + if (aux) + split = g_strsplit (aux, ",", -1); + } + + if (!split) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "%%IPSYS=? response didn't match"); + return NULL; + } + + for (i = 0; split[i]; i++) { + gchar *interval_separator; + + g_strstrip (split[i]); + interval_separator = strstr (split[i], "-"); + if (interval_separator) { + /* Add all in interval */ + gchar *first, *last; + guint modefirst, modelast; + + first = g_strdup (split[i]); + interval_separator = strstr (first, "-"); + *(interval_separator++) = '\0'; + last = interval_separator; + + if (mm_get_uint_from_str (first, &modefirst) && + mm_get_uint_from_str (last, &modelast) && + modefirst < modelast && + modelast <= 5) { + guint j; + + for (j = modefirst; j <= modelast; j++) + add_supported_mode (MM_BROADBAND_MODEM_ICERA (self), &combinations, j); + } else + mm_obj_warn (self, "couldn't parse mode interval in %%IPSYS=? response: %s", split[i]); + g_free (first); + } else { + guint mode; + + /* Add single */ + if (mm_get_uint_from_str (split[i], &mode)) + add_supported_mode (MM_BROADBAND_MODEM_ICERA (self), &combinations, mode); + else + mm_obj_warn (self, "couldn't parse mode in %%IPSYS=? response: %s", split[i]); + } + } + + g_strfreev (split); + + if (!combinations) + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "No mode combinations were parsed from the %%IPSYS=? response (%s)", + response); + + return combinations; +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "%IPSYS=?", + 3, + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +static gboolean +modem_load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + const gchar *response; + const gchar *str; + gint mode, domain; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + str = mm_strip_tag (response, "%IPSYS:"); + + if (!sscanf (str, "%d,%d", &mode, &domain)) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse %%IPSYS response: '%s'", + response); + return FALSE; + } + + switch (mode) { + case 0: + *allowed = MM_MODEM_MODE_2G; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + case 1: + *allowed = MM_MODEM_MODE_3G; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + case 2: + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_2G; + return TRUE; + case 3: + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_3G; + return TRUE; + case 5: /* any */ + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + default: + break; + } + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse unexpected %%IPSYS response: '%s'", + response); + return FALSE; +} + +static void +modem_load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "%IPSYS?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +static gboolean +modem_set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +allowed_mode_update_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (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); +} + +static void +modem_set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *command; + gint icera_mode = -1; + + task = g_task_new (self, NULL, callback, user_data); + + /* + * The core has checked the following: + * - that 'allowed' are a subset of the 'supported' modes + * - that 'preferred' is one mode, and a subset of 'allowed' + */ + if (allowed == MM_MODEM_MODE_2G) + icera_mode = 0; + else if (allowed == MM_MODEM_MODE_3G) + icera_mode = 1; + else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { + if (preferred == MM_MODEM_MODE_2G) + icera_mode = 2; + else if (preferred == MM_MODEM_MODE_3G) + icera_mode = 3; + else /* none preferred, so AUTO */ + icera_mode = 5; + } else if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE) + icera_mode = 5; + + if (icera_mode < 0) { + 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_object_unref (task); + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("%%IPSYS=%d", icera_mode); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)allowed_mode_update_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Icera-specific unsolicited events handling */ + +typedef struct { + guint cid; + MMBearerConnectionStatus status; +} BearerListReportStatusForeachContext; + +static void +bearer_list_report_status_foreach (MMBaseBearer *bearer, + BearerListReportStatusForeachContext *ctx) +{ + gint profile_id; + gint connecting_profile_id; + + if (!MM_IS_BROADBAND_BEARER_ICERA (bearer)) + return; + + /* The profile ID in the base bearer is set only once the modem is connected */ + profile_id = mm_base_bearer_get_profile_id (bearer); + + /* The profile ID in the icera bearer is available during the connecting phase */ + connecting_profile_id = mm_broadband_bearer_icera_get_connecting_profile_id (MM_BROADBAND_BEARER_ICERA (bearer)); + + if ((profile_id != (gint)ctx->cid) && (connecting_profile_id != (gint)ctx->cid)) + return; + + mm_base_bearer_report_connection_status (bearer, ctx->status); +} + +static void +ipdpact_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemIcera *self) +{ + MMBearerList *list = NULL; + BearerListReportStatusForeachContext ctx; + guint cid; + guint status; + + /* Ensure we got proper parsed values */ + if (!mm_get_uint_from_match_info (match_info, 1, &cid) || + !mm_get_uint_from_match_info (match_info, 2, &status)) + return; + + /* Setup context */ + ctx.cid = cid; + ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN; + + switch (status) { + case 0: + ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED; + break; + case 1: + ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED; + break; + case 2: + /* activating */ + break; + case 3: + ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED; + break; + default: + mm_obj_warn (self, "unknown %%IPDPACT connect status %d", status); + break; + } + + /* If unknown status, don't try to report anything */ + if (ctx.status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) + return; + + /* If empty bearer list, nothing else to do */ + g_object_get (self, + MM_IFACE_MODEM_BEARER_LIST, &list, + NULL); + if (!list) + return; + + /* Will report status only in the bearer with the specific CID */ + mm_bearer_list_foreach (list, + (MMBearerListForeachFunc)bearer_list_report_status_foreach, + &ctx); + g_object_unref (list); +} + +static MMModemAccessTechnology +nwstate_to_act (const gchar *str) +{ + /* small 'g' means CS, big 'G' means PS */ + if (!strcmp (str, "2g")) + return MM_MODEM_ACCESS_TECHNOLOGY_GSM; + else if (!strcmp (str, "2G-GPRS")) + return MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + else if (!strcmp (str, "2G-EDGE")) + return MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + else if (!strcmp (str, "3G")) + return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + else if (!strcmp (str, "3g")) + return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + else if (!strcmp (str, "R99")) + return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + else if (!strcmp (str, "3G-HSDPA") || !strcmp (str, "HSDPA")) + return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; + else if (!strcmp (str, "3G-HSUPA") || !strcmp (str, "HSUPA")) + return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA; + else if (!strcmp (str, "3G-HSDPA-HSUPA") || !strcmp (str, "HSDPA-HSUPA")) + return MM_MODEM_ACCESS_TECHNOLOGY_HSPA; + else if (!strcmp (str, "3G-HSDPA-HSUPA-HSPA+") || !strcmp (str, "HSDPA-HSUPA-HSPA+")) + return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; + + return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; +} + +static void +nwstate_changed (MMPortSerialAt *port, + GMatchInfo *info, + MMBroadbandModemIcera *self) +{ + gchar *str; + + /* + * %NWSTATE: <rssi>,<mccmnc>,<tech>,<connection state>,<regulation> + * + * <connection state> shows the actual access technology in-use when a + * PS connection is active. + */ + + /* Process signal quality... */ + str = g_match_info_fetch (info, 1); + if (str) { + gint rssi; + + rssi = atoi (str); + rssi = CLAMP (rssi, 0, 5) * 100 / 5; + g_free (str); + + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), + (guint)rssi); + } + + /* Process access technology... */ + str = g_match_info_fetch (info, 4); + if (!str || (strcmp (str, "-") == 0)) { + g_free (str); + str = g_match_info_fetch (info, 3); + } + if (str) { + MMModemAccessTechnology act; + + act = nwstate_to_act (str); + g_free (str); + + /* Cache last received value, needed for explicit access technology + * query handling */ + self->priv->last_act = act; + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + act, + MM_MODEM_ACCESS_TECHNOLOGY_ANY); + } +} + +static void +set_unsolicited_events_handlers (MMBroadbandModemIcera *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* Access technology related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->nwstate_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)nwstate_changed : NULL, + enable ? self : NULL, + NULL); + + /* Connection status related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->ipdpact_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)ipdpact_received : NULL, + enable ? self : NULL, + NULL); + + /* Always to ignore */ + if (!enable) { + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->pacsp_regex, + NULL, + NULL, + NULL); + } + } +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +static gboolean +modem_load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + *access_technologies = (MMModemAccessTechnology) value; + *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; + return TRUE; +} + +static void +nwstate_query_ready (MMBroadbandModemIcera *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) + g_task_return_error (task, error); + else { + /* + * The unsolicited message handler will already have run and + * removed the NWSTATE response, so we use the result from there. + */ + g_task_return_int (task, self->priv->last_act); + } + + g_object_unref (task); +} + +static void +modem_load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "%NWSTATE", + 3, + FALSE, + (GAsyncReadyCallback)nwstate_query_ready, + task); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_ICERA (self), TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static void +parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own cleanup first */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_ICERA (self), FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Enable/disable unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_enable_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +own_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Our own enable now */ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "%NWSTATE=1", + 3, + FALSE, + (GAsyncReadyCallback)own_enable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's enable */ + iface_modem_3gpp_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static void +parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +own_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Next, chain up parent's disable */ + iface_modem_3gpp_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "%NWSTATE=0", + 3, + FALSE, + (GAsyncReadyCallback)own_disable_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Create bearer (Modem interface) */ + +static MMBaseBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_icera_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_icera_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + + g_object_unref (task); +} + +static void +broadband_bearer_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + + g_object_unref (task); +} + +static void +modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *props, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* If we get a NET port, create Icera bearer */ + if (mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) { + mm_broadband_bearer_icera_new ( + MM_BROADBAND_MODEM (self), + MM_BROADBAND_MODEM_ICERA (self)->priv->default_ip_method, + props, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_icera_new_ready, + task); + return; + } + + /* Otherwise, plain generic broadband bearer */ + mm_broadband_bearer_new ( + MM_BROADBAND_MODEM (self), + props, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_new_ready, + task); +} + +/*****************************************************************************/ +/* Modem power up (Modem interface) */ + +static gboolean +modem_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cfun_enable_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) { + /* Ignore all errors except NOT_ALLOWED, which means Airplane Mode */ + if (g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NOT_ALLOWED)) + g_task_return_error (task, error); + else { + g_error_free (error); + g_task_return_boolean (task, TRUE); + } + } + + g_object_unref (task); +} + +static void +modem_power_up (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=1", + 10, + FALSE, + (GAsyncReadyCallback)cfun_enable_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Use AT+CFUN=4 for power down. It will stop the RF (IMSI detach), and + * keeps access to the SIM */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=4", + /* The modem usually completes +CFUN=4 within 1-2 seconds, + * but sometimes takes a ridiculously long time (~30-35 seconds). + * It's better to have a long timeout here than to have the + * modem not responding to subsequent AT commands until +CFUN=4 + * completes. */ + 40, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Reset (Modem interface) */ + +static gboolean +modem_reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "%IRESET", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +static MMUnlockRetries * +modem_load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_unlock_retries_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + int pin1, puk1, pin2, puk2; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + response = mm_strip_tag (response, "%PINNUM:"); + if (sscanf (response, " %d, %d, %d, %d", &pin1, &puk1, &pin2, &puk2) == 4) { + MMUnlockRetries *retries; + retries = mm_unlock_retries_new (); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2); + g_task_return_pointer (task, retries, g_object_unref); + } else { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid unlock retries response: '%s'", + response); + } + g_object_unref (task); +} + +static void +modem_load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "%PINNUM?", + 3, + FALSE, + (GAsyncReadyCallback)load_unlock_retries_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Generic band handling utilities */ + +typedef struct { + MMModemBand band; + char *name; + gboolean enabled; +} Band; + +static void +band_free (Band *b) +{ + g_free (b->name); + g_free (b); +} + +static const Band modem_bands[] = { + /* Sort 3G first since it's preferred */ + { MM_MODEM_BAND_UTRAN_1, (gchar *) "FDD_BAND_I", FALSE }, + { MM_MODEM_BAND_UTRAN_2, (gchar *) "FDD_BAND_II", FALSE }, + { MM_MODEM_BAND_UTRAN_3, (gchar *) "FDD_BAND_III", FALSE }, + { MM_MODEM_BAND_UTRAN_4, (gchar *) "FDD_BAND_IV", FALSE }, + { MM_MODEM_BAND_UTRAN_5, (gchar *) "FDD_BAND_V", FALSE }, + { MM_MODEM_BAND_UTRAN_6, (gchar *) "FDD_BAND_VI", FALSE }, + { MM_MODEM_BAND_UTRAN_8, (gchar *) "FDD_BAND_VIII", FALSE }, + /* 2G second */ + { MM_MODEM_BAND_G850, (gchar *) "G850", FALSE }, + { MM_MODEM_BAND_DCS, (gchar *) "DCS", FALSE }, + { MM_MODEM_BAND_EGSM, (gchar *) "EGSM", FALSE }, + { MM_MODEM_BAND_PCS, (gchar *) "PCS", FALSE }, + /* And ANY last since it's most inclusive */ + { MM_MODEM_BAND_ANY, (gchar *) "ANY", FALSE }, +}; + +static const guint modem_band_any_bit = 1 << (G_N_ELEMENTS (modem_bands) - 1); + +static MMModemBand +icera_band_to_mm (const char *icera) +{ + guint i; + + for (i = 0 ; i < G_N_ELEMENTS (modem_bands); i++) { + if (g_strcmp0 (icera, modem_bands[i].name) == 0) + return modem_bands[i].band; + } + return MM_MODEM_BAND_UNKNOWN; +} + +static GSList * +parse_bands (const gchar *response, + guint32 *out_len) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) info = NULL; + GSList *bands = NULL; + + g_return_val_if_fail (out_len != NULL, NULL); + + /* + * Response is a number of lines of the form: + * "EGSM": 0 + * "FDD_BAND_I": 1 + * ... + * with 1 and 0 indicating whether the particular band is enabled or not. + */ + r = g_regex_new ("^\"(\\w+)\": (\\d)", + G_REGEX_MULTILINE, G_REGEX_MATCH_NEWLINE_ANY, + NULL); + g_assert (r != NULL); + + g_regex_match (r, response, 0, &info); + while (g_match_info_matches (info)) { + gchar *name, *enabled; + Band *b; + MMModemBand band; + + name = g_match_info_fetch (info, 1); + enabled = g_match_info_fetch (info, 2); + band = icera_band_to_mm (name); + if (band != MM_MODEM_BAND_UNKNOWN) { + b = g_malloc0 (sizeof (Band)); + b->band = band; + b->name = g_strdup (name); + b->enabled = (enabled[0] == '1' ? TRUE : FALSE); + bands = g_slist_append (bands, b); + *out_len = *out_len + 1; + } + g_free (name); + g_free (enabled); + g_match_info_next (info, NULL); + } + + return bands; +} + +/*****************************************************************************/ +/* Load supported bands (Modem interface) */ + +typedef struct { + MMBaseModemAtCommandAlloc *cmds; + GSList *check_bands; + GSList *enabled_bands; + guint32 idx; +} SupportedBandsContext; + +static void +supported_bands_context_free (SupportedBandsContext *ctx) +{ + guint i; + + for (i = 0; ctx->cmds[i].command; i++) + mm_base_modem_at_command_alloc_clear (&ctx->cmds[i]); + g_free (ctx->cmds); + g_slist_free_full (ctx->check_bands, (GDestroyNotify) band_free); + g_slist_free_full (ctx->enabled_bands, (GDestroyNotify) band_free); + g_free (ctx); +} + +static GArray * +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) +{ + GError *error = NULL; + SupportedBandsContext *ctx = NULL; + GArray *bands; + GSList *iter; + + mm_base_modem_at_sequence_finish (self, res, (gpointer) &ctx, &error); + if (error) + g_task_return_error (task, error); + else { + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), ctx->idx); + + /* Add already enabled bands */ + for (iter = ctx->enabled_bands; iter; iter = g_slist_next (iter)) { + Band *b = iter->data; + + g_array_prepend_val (bands, b->band); + } + + /* Add any checked bands that are supported */ + for (iter = ctx->check_bands; iter; iter = g_slist_next (iter)) { + Band *b = iter->data; + + /* 'enabled' here really means supported/unsupported */ + if (b->enabled) + g_array_prepend_val (bands, b->band); + } + + g_task_return_pointer (task, bands, (GDestroyNotify) g_array_unref); + } + g_object_unref (task); +} + +static MMBaseModemAtResponseProcessorResult +load_supported_bands_response_processor (MMBaseModem *self, + gpointer context, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + SupportedBandsContext *ctx; + Band *b; + + ctx = context; + b = g_slist_nth_data (ctx->check_bands, ctx->idx++); + + /* If there was no error setting the band, that band is supported. We + * abuse the 'enabled' item to mean supported/unsupported. + */ + b->enabled = !error; + + /* Continue to next band */ + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; +} + +static void +load_supported_bands_get_current_bands_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + SupportedBandsContext *ctx; + const gchar *response; + GError *error = NULL; + GSList *iter, *new; + guint32 len = 0, i = 0; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_new0 (SupportedBandsContext, 1); + + /* For each reported band, build up an AT command to set that band + * to its current enabled/disabled state. + */ + iter = ctx->check_bands = parse_bands (response, &len); + ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, len + 1); + + while (iter) { + Band *b = iter->data; + + if (b->enabled || b->band == MM_MODEM_BAND_ANY) { + /* Move known-supported band to the enabled list */ + new = g_slist_next (iter); + ctx->check_bands = g_slist_remove_link (ctx->check_bands, iter); + ctx->enabled_bands = g_slist_prepend (ctx->enabled_bands, iter->data); + g_slist_free (iter); + iter = new; + } else { + /* Check support for disabled band */ + ctx->cmds[i].command = g_strdup_printf ("%%IPBM=\"%s\",0", b->name); + ctx->cmds[i].timeout = 10; + ctx->cmds[i].allow_cached = FALSE; + ctx->cmds[i].response_processor = load_supported_bands_response_processor; + i++; + iter = g_slist_next (iter); + } + } + + mm_base_modem_at_sequence (MM_BASE_MODEM (self), + (const MMBaseModemAtCommand *)ctx->cmds, + ctx, + (GDestroyNotify) supported_bands_context_free, + (GAsyncReadyCallback) load_supported_bands_ready, + task); +} + +static void +modem_load_supported_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* The modems report some bands as disabled that they don't actually + * support enabling. Thanks Icera! So we have to try setting each + * band to it's current enabled/disabled value, and the modem will + * return an error if it doesn't support that band at all. + */ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "%IPBM?", + 3, + FALSE, + (GAsyncReadyCallback)load_supported_bands_get_current_bands_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load current bands (Modem interface) */ + +static GArray * +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 (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GArray *bands; + const gchar *response; + GError *error = NULL; + GSList *parsed, *iter; + guint32 len = 0; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + } else { + /* Parse bands from Icera response into MM band numbers */ + parsed = parse_bands (response, &len); + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), len); + for (iter = parsed; iter; iter = g_slist_next (iter)) { + Band *b = iter->data; + + if (b->enabled) + g_array_append_val (bands, b->band); + } + g_slist_free_full (parsed, (GDestroyNotify) band_free); + + g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); + } + g_object_unref (task); +} + +static void +modem_load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "%IPBM?", + 3, + FALSE, + (GAsyncReadyCallback)load_current_bands_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Set current bands (Modem interface) */ + +typedef struct { + guint bandbits; + guint enablebits; + guint disablebits; +} SetCurrentBandsContext; + +/* + * The modem's band-setting command (%IPBM=) enables or disables one + * band at a time, and one band must always be enabled. Here, we first + * get the set of enabled bands, compute the difference between that + * set and the requested set, enable any added bands, and finally + * disable any removed bands. + */ +static gboolean +modem_set_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void set_one_band (MMIfaceModem *self, GTask *task); + +static void +set_current_bands_next (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + set_one_band (self, task); +} + +static void +set_one_band (MMIfaceModem *self, + GTask *task) +{ + SetCurrentBandsContext *ctx; + guint enable, band; + gchar *command; + + ctx = g_task_get_task_data (task); + + /* Find the next band to enable or disable, always doing enables first */ + enable = 1; + band = ffs (ctx->enablebits); + if (band == 0) { + enable = 0; + band = ffs (ctx->disablebits); + } + if (band == 0) { + /* Both enabling and disabling are done */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Note that ffs() returning 2 corresponds to 1 << 1, not 1 << 2 */ + band--; + mm_obj_dbg (self, "preparing %%IPBM command (1/2): enablebits %x, disablebits %x, band %d, enable %d", + ctx->enablebits, ctx->disablebits, band, enable); + + if (enable) + ctx->enablebits &= ~(1 << band); + else + ctx->disablebits &= ~(1 << band); + mm_obj_dbg (self, "preparing %%IPBM command (2/2): enablebits %x, disablebits %x", + ctx->enablebits, ctx->disablebits); + + command = g_strdup_printf ("%%IPBM=\"%s\",%d", + modem_bands[band].name, + enable); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 10, + FALSE, + (GAsyncReadyCallback)set_current_bands_next, + task); + g_free (command); +} + +static guint +band_array_to_bandbits (GArray *bands) +{ + MMModemBand band; + guint i, j, bandbits; + + bandbits = 0; + for (i = 0 ; i < bands->len ; i++) { + band = g_array_index (bands, MMModemBand, i); + for (j = 0 ; j < G_N_ELEMENTS (modem_bands) ; j++) { + if (modem_bands[j].band == band) { + bandbits |= 1 << j; + break; + } + } + g_assert (j < G_N_ELEMENTS (modem_bands)); + } + + return bandbits; +} + +static void +set_current_bands_got_current_bands (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + SetCurrentBandsContext *ctx; + GArray *bands; + GError *error = NULL; + guint currentbits; + + bands = modem_load_current_bands_finish (self, res, &error); + if (!bands) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + currentbits = band_array_to_bandbits (bands); + ctx->enablebits = ctx->bandbits & ~currentbits; + ctx->disablebits = currentbits & ~ctx->bandbits; + + set_one_band (self, task); +} + +static void +modem_set_current_bands (MMIfaceModem *self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SetCurrentBandsContext *ctx; + GTask *task; + + ctx = g_new0 (SetCurrentBandsContext, 1); + ctx->bandbits = band_array_to_bandbits (bands_array); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + /* + * If ANY is requested, simply enable ANY to activate all bands except for + * those forbidden. */ + if (ctx->bandbits & modem_band_any_bit) { + ctx->enablebits = modem_band_any_bit; + ctx->disablebits = 0; + set_one_band (self, task); + return; + } + + modem_load_current_bands (self, + (GAsyncReadyCallback)set_current_bands_got_current_bands, + task); +} + +/*****************************************************************************/ +/* Load network timezone (Time interface) */ + +static gboolean +parse_tlts_query_reply (const gchar *response, + gchar **iso8601, + MMNetworkTimezone **tz, + GError **error) +{ + gboolean ret = TRUE; + gint year; + gint month; + gint day; + gint hour; + gint minute; + gint second; + gchar sign; + gint offset; + GDateTime *utc, *adjusted; + + /* TLTS reports UTC time with the TZ offset to *local* time */ + response = mm_strip_tag (response, "*TLTS: "); + if (sscanf (response, + "\"%02d/%02d/%02d,%02d:%02d:%02d%c%02d\"", + &year, + &month, + &day, + &hour, + &minute, + &second, + &sign, + &offset) != 8) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unknown *TLTS response: %s", + response); + return FALSE; + } + + /* Icera modems only report a 2-digit year, while ISO-8601 requires + * a 4-digit year. Assume 2000. + */ + if (year < 100) + year += 2000; + + /* Offset comes in 15-min units */ + offset *= 15; + /* Apply sign to offset; */ + if (sign == '-') + offset *= -1; + + utc = g_date_time_new_utc (year, month, day, hour, minute, second); + if (!utc) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid *TLTS date/time: %s", + response); + return FALSE; + } + + /* Convert UTC time to local time by adjusting by the timezone offset */ + adjusted = g_date_time_add_minutes (utc, offset); + g_date_time_unref (utc); + if (!adjusted) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to convert modem time to local time (offset %d)", + offset); + return FALSE; + } + + /* Convert offset from minutes-to-UTC to minutes-from-UTC */ + offset *= -1; + + if (tz) { + *tz = mm_network_timezone_new (); + mm_network_timezone_set_offset (*tz, offset); + } + + if (iso8601) { + *iso8601 = mm_new_iso8601_time (g_date_time_get_year (adjusted), + g_date_time_get_month (adjusted), + g_date_time_get_day_of_month (adjusted), + g_date_time_get_hour (adjusted), + g_date_time_get_minute (adjusted), + g_date_time_get_second (adjusted), + TRUE, + offset, + error); + ret = (*iso8601 != NULL); + } + + g_date_time_unref (adjusted); + return ret; +} + +static MMNetworkTimezone * +modem_time_load_network_timezone_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + const gchar *response; + MMNetworkTimezone *tz; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL); + if (!response) { + /* We'll assume we can retry a bit later */ + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_RETRY, + "Retry"); + return NULL; + } + + return (parse_tlts_query_reply (response, NULL, &tz, error) ? tz : NULL); +} + +static void +modem_time_load_network_timezone (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*TLTS", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* List profiles (3GPP profile management interface) */ + +typedef struct { + GList *profiles; +} ListProfilesContext; + +static void +list_profiles_context_free (ListProfilesContext *ctx) +{ + mm_3gpp_profile_list_free (ctx->profiles); + g_slice_free (ListProfilesContext, ctx); +} + +static gboolean +modem_3gpp_profile_manager_list_profiles_finish (MMIfaceModem3gppProfileManager *self, + GAsyncResult *res, + GList **out_profiles, + GError **error) +{ + ListProfilesContext *ctx; + + if (!g_task_propagate_boolean (G_TASK (res), error)) + return FALSE; + + ctx = g_task_get_task_data (G_TASK (res)); + if (out_profiles) + *out_profiles = g_steal_pointer (&ctx->profiles); + return TRUE; +} + +static void +profile_manager_ipdpcfg_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + ListProfilesContext *ctx; + const gchar *response; + g_autoptr(GError) error = NULL; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) + mm_obj_warn (self, "couldn't load PDP context auth settings: %s", error->message); + else if (!mm_icera_parse_ipdpcfg_query_response (response, ctx->profiles, self, &error)) + mm_obj_warn (self, "couldn't update profile list with PDP context auth settings: %s", error->message); + + /* complete successfully anyway */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +profile_manager_parent_list_profiles_ready (MMIfaceModem3gppProfileManager *self, + GAsyncResult *res, + GTask *task) +{ + ListProfilesContext *ctx; + GError *error = NULL; + + ctx = g_slice_new0 (ListProfilesContext); + g_task_set_task_data (task, ctx, (GDestroyNotify) list_profiles_context_free); + + if (!iface_modem_3gpp_profile_manager_parent->list_profiles_finish (self, res, &ctx->profiles, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!ctx->profiles) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "%IPDPCFG?", + 3, + FALSE, + (GAsyncReadyCallback)profile_manager_ipdpcfg_query_ready, + task); +} + +static void +modem_3gpp_profile_manager_list_profiles (MMIfaceModem3gppProfileManager *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + iface_modem_3gpp_profile_manager_parent->list_profiles ( + self, + (GAsyncReadyCallback)profile_manager_parent_list_profiles_ready, + task); +} + +typedef struct { + gboolean new_id; + gint min_profile_id; + gint max_profile_id; + GEqualFunc apn_cmp; + MM3gppProfileCmpFlags profile_cmp_flags; +} CheckFormatContext; + +static void +check_format_context_free (CheckFormatContext *ctx) +{ + g_slice_free (CheckFormatContext, ctx); +} + +static gboolean +modem_3gpp_profile_manager_check_format_finish (MMIfaceModem3gppProfileManager *self, + GAsyncResult *res, + gboolean *new_id, + gint *min_profile_id, + gint *max_profile_id, + GEqualFunc *apn_cmp, + MM3gppProfileCmpFlags *profile_cmp_flags, + GError **error) +{ + CheckFormatContext *ctx; + + if (!g_task_propagate_boolean (G_TASK (res), error)) + return FALSE; + + ctx = g_task_get_task_data (G_TASK (res)); + if (new_id) + *new_id = ctx->new_id; + if (min_profile_id) + *min_profile_id = (gint) ctx->min_profile_id; + if (max_profile_id) + *max_profile_id = (gint) ctx->max_profile_id; + if (apn_cmp) + *apn_cmp = ctx->apn_cmp; + if (profile_cmp_flags) + *profile_cmp_flags = ctx->profile_cmp_flags; + return TRUE; +} + +static void +profile_manager_parent_check_format_ready (MMIfaceModem3gppProfileManager *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + CheckFormatContext *ctx; + + ctx = g_task_get_task_data (task); + + if (!iface_modem_3gpp_profile_manager_parent->check_format_finish (self, + res, + &ctx->new_id, + &ctx->min_profile_id, + &ctx->max_profile_id, + &ctx->apn_cmp, + &ctx->profile_cmp_flags, + &error)) { + g_task_return_error (task, error); + } else { + /* the icera implementation supports AUTH, so unset that cmp flag */ + ctx->profile_cmp_flags &= ~MM_3GPP_PROFILE_CMP_FLAGS_NO_AUTH; + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_profile_manager_check_format (MMIfaceModem3gppProfileManager *self, + MMBearerIpFamily ip_type, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + CheckFormatContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + ctx = g_slice_new0 (CheckFormatContext); + g_task_set_task_data (task, ctx, (GDestroyNotify)check_format_context_free); + + iface_modem_3gpp_profile_manager_parent->check_format ( + self, + ip_type, + (GAsyncReadyCallback)profile_manager_parent_check_format_ready, + task); +} + +/*****************************************************************************/ +/* Deactivate profile (3GPP profile management interface) */ + +static gboolean +modem_3gpp_profile_manager_deactivate_profile_finish (MMIfaceModem3gppProfileManager *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +deactivate_profile_ipdpact_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_3gpp_profile_manager_deactivate_profile (MMIfaceModem3gppProfileManager *self, + MM3gppProfile *profile, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + g_autofree gchar *cmd = NULL; + gint profile_id; + + task = g_task_new (self, NULL, callback, user_data); + + profile_id = mm_3gpp_profile_get_profile_id (profile); + mm_obj_dbg (self, "deactivating profile '%d'...", profile_id); + + cmd = g_strdup_printf ("%%IPDPACT=%d,0", profile_id); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + cmd, + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, + FALSE, + (GAsyncReadyCallback)deactivate_profile_ipdpact_set_ready, + task); +} + +/*****************************************************************************/ +/* Set profile (3GPP profile management interface) */ + +#define IPDPCFG_SET_MAX_ATTEMPTS 3 +#define IPDPCFG_SET_RETRY_TIMEOUT_SECS 1 + +typedef struct { + MM3gppProfile *profile; + gchar *cmd; + gint profile_id; + guint n_attempts; +} StoreProfileContext; + +static void +store_profile_context_free (StoreProfileContext *ctx) +{ + g_free (ctx->cmd); + g_clear_object (&ctx->profile); + g_slice_free (StoreProfileContext, ctx); +} + +static gint +modem_3gpp_profile_manager_store_profile_finish (MMIfaceModem3gppProfileManager *self, + GAsyncResult *res, + gint *out_profile_id, + MMBearerApnType *out_apn_type, + GError **error) +{ + StoreProfileContext *ctx; + + if (!g_task_propagate_boolean (G_TASK (res), error)) + return FALSE; + + ctx = g_task_get_task_data (G_TASK (res)); + if (out_profile_id) + *out_profile_id = ctx->profile_id; + if (out_apn_type) + *out_apn_type = MM_BEARER_APN_TYPE_NONE; + return TRUE; +} + +static void profile_manager_store_profile_auth_settings (GTask *task); + +static gboolean +profile_manager_ipdpcfg_set_retry (GTask *task) +{ + profile_manager_store_profile_auth_settings (task); + return G_SOURCE_REMOVE; +} + +static void +profile_manager_ipdpcfg_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + StoreProfileContext *ctx; + g_autoptr(GError) error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + /* Retry configuring the context. It sometimes fails with a 583 + * error ["a profile (CID) is currently active"] if a connect + * is attempted too soon after a disconnect. */ + if (ctx->n_attempts < IPDPCFG_SET_MAX_ATTEMPTS) { + mm_obj_dbg (self, "couldn't store auth settings in profile '%d': %s; retrying...", + ctx->profile_id, error->message); + g_timeout_add_seconds (IPDPCFG_SET_RETRY_TIMEOUT_SECS, (GSourceFunc)profile_manager_ipdpcfg_set_retry, task); + return; + } + g_task_return_error (task, g_steal_pointer (&error)); + } else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +profile_manager_store_profile_auth_settings (GTask *task) +{ + MMIfaceModem3gppProfileManager *self; + StoreProfileContext *ctx; + g_autofree gchar *cmd = NULL; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + if (!ctx->cmd) { + const gchar *user; + const gchar *password; + MMBearerAllowedAuth allowed_auth; + + user = mm_3gpp_profile_get_user (ctx->profile); + password = mm_3gpp_profile_get_password (ctx->profile); + allowed_auth = mm_3gpp_profile_get_allowed_auth (ctx->profile); + + /* Both user and password are required; otherwise firmware returns an error */ + if (!user || !password || allowed_auth == MM_BEARER_ALLOWED_AUTH_NONE) { + mm_obj_dbg (self, "not using authentication"); + ctx->cmd = g_strdup_printf ("%%IPDPCFG=%d,0,0,\"\",\"\"", ctx->profile_id); + } else { + g_autofree gchar *quoted_user = NULL; + g_autofree gchar *quoted_password = NULL; + guint icera_auth; + + if (allowed_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN) { + mm_obj_dbg (self, "using default (CHAP) authentication method"); + icera_auth = 2; + } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_CHAP) { + mm_obj_dbg (self, "using CHAP authentication method"); + icera_auth = 2; + } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_PAP) { + mm_obj_dbg (self, "using PAP authentication method"); + icera_auth = 1; + } else { + g_autofree gchar *str = NULL; + + str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot use any of the specified authentication methods (%s)", + str); + g_object_unref (task); + return; + } + + quoted_user = mm_port_serial_at_quote_string (user); + quoted_password = mm_port_serial_at_quote_string (password); + ctx->cmd = g_strdup_printf ("%%IPDPCFG=%d,0,%u,%s,%s", + ctx->profile_id, + icera_auth, + quoted_user, + quoted_password); + } + } + + ctx->n_attempts++; + mm_base_modem_at_command (MM_BASE_MODEM (self), + ctx->cmd, + 6, + FALSE, + (GAsyncReadyCallback)profile_manager_ipdpcfg_set_ready, + task); +} + +static void +profile_manager_parent_store_profile_ready (MMIfaceModem3gppProfileManager *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_profile_manager_parent->store_profile_finish (self, res, NULL, NULL, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + profile_manager_store_profile_auth_settings (task); +} + +static void +modem_3gpp_profile_manager_store_profile (MMIfaceModem3gppProfileManager *self, + MM3gppProfile *profile, + const gchar *index_field, + GAsyncReadyCallback callback, + gpointer user_data) +{ + StoreProfileContext *ctx; + GTask *task; + + g_assert (g_strcmp0 (index_field, "profile-id") == 0); + + task = g_task_new (self, NULL, callback, user_data); + ctx = g_slice_new0 (StoreProfileContext); + ctx->profile = g_object_ref (profile); + ctx->profile_id = mm_3gpp_profile_get_profile_id (ctx->profile); + g_assert (ctx->profile_id != MM_3GPP_PROFILE_ID_UNKNOWN); + g_task_set_task_data (task, ctx, (GDestroyNotify) store_profile_context_free); + + iface_modem_3gpp_profile_manager_parent->store_profile ( + self, + profile, + index_field, + (GAsyncReadyCallback)profile_manager_parent_store_profile_ready, + task); +} + +/*****************************************************************************/ +/* Load network time (Time interface) */ + +static gchar * +modem_time_load_network_time_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + const gchar *response; + gchar *iso8601; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return NULL; + + return (parse_tlts_query_reply (response, &iso8601, NULL, error) ? iso8601 : NULL); +} + +static void +modem_time_load_network_time (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*TLTS", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Check support (Time interface) */ + +static gboolean +modem_time_check_support_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +modem_time_check_support (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* We assume Icera devices always support *TLTS, since they appear + * to return ERROR if the modem is not powered up, and thus we cannot + * check for *TLTS support during modem initialization. + */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *self) +{ + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_icera_parent_class)->setup_ports (self); + + /* Now reset the unsolicited messages we'll handle when enabled */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_ICERA (self), FALSE); +} + +/*****************************************************************************/ + +MMBroadbandModemIcera * +mm_broadband_modem_icera_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_ICERA, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer (AT) or Icera bearer (NET) supported */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MMBroadbandModemIcera *self = MM_BROADBAND_MODEM_ICERA (object); + + switch (prop_id) { + case PROP_DEFAULT_IP_METHOD: + self->priv->default_ip_method = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MMBroadbandModemIcera *self = MM_BROADBAND_MODEM_ICERA (object); + + switch (prop_id) { + case PROP_DEFAULT_IP_METHOD: + g_value_set_enum (value, self->priv->default_ip_method); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +mm_broadband_modem_icera_init (MMBroadbandModemIcera *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_ICERA, + MMBroadbandModemIceraPrivate); + + self->priv->nwstate_regex = g_regex_new ("%NWSTATE:\\s*(-?\\d+),(\\d+),([^,]*),([^,]*),(\\d+)", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->pacsp_regex = g_regex_new ("\\r\\n\\+PACSP(\\d)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ipdpact_regex = g_regex_new ("\\r\\n%IPDPACT:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + self->priv->default_ip_method = MM_BEARER_IP_METHOD_STATIC; + self->priv->last_act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemIcera *self = MM_BROADBAND_MODEM_ICERA (object); + + g_regex_unref (self->priv->nwstate_regex); + g_regex_unref (self->priv->pacsp_regex); + g_regex_unref (self->priv->ipdpact_regex); + + G_OBJECT_CLASS (mm_broadband_modem_icera_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = modem_load_current_modes; + iface->load_current_modes_finish = modem_load_current_modes_finish; + iface->set_current_modes = modem_set_current_modes; + iface->set_current_modes_finish = modem_set_current_modes_finish; + iface->load_access_technologies = modem_load_access_technologies; + iface->load_access_technologies_finish = modem_load_access_technologies_finish; + iface->load_unlock_retries = modem_load_unlock_retries; + iface->load_unlock_retries_finish = modem_load_unlock_retries_finish; + iface->load_supported_bands = modem_load_supported_bands; + iface->load_supported_bands_finish = modem_load_supported_bands_finish; + iface->load_current_bands = modem_load_current_bands; + iface->load_current_bands_finish = modem_load_current_bands_finish; + iface->modem_power_up = modem_power_up; + iface->modem_power_up_finish = modem_power_up_finish; + /* Note: don't implement modem_init_power_down, as CFUN=4 here may take + * looong to reply */ + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; + iface->reset = modem_reset; + iface->reset_finish = modem_reset_finish; + iface->set_current_bands = modem_set_current_bands; + iface->set_current_bands_finish = modem_set_current_bands_finish; + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish; +} + +static void +iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManager *iface) +{ + iface_modem_3gpp_profile_manager_parent = g_type_interface_peek_parent (iface); + + iface->list_profiles = modem_3gpp_profile_manager_list_profiles; + iface->list_profiles_finish = modem_3gpp_profile_manager_list_profiles_finish; + iface->check_format = modem_3gpp_profile_manager_check_format; + iface->check_format_finish = modem_3gpp_profile_manager_check_format_finish; + /* note: the parent check_activated_profile() implementation using +CGACT? seems to + * be perfectly valid. */ + iface->deactivate_profile = modem_3gpp_profile_manager_deactivate_profile; + iface->deactivate_profile_finish = modem_3gpp_profile_manager_deactivate_profile_finish; + iface->store_profile = modem_3gpp_profile_manager_store_profile; + iface->store_profile_finish = modem_3gpp_profile_manager_store_profile_finish; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface->check_support = modem_time_check_support; + iface->check_support_finish = modem_time_check_support_finish; + iface->load_network_time = modem_time_load_network_time; + iface->load_network_time_finish = modem_time_load_network_time_finish; + iface->load_network_timezone = modem_time_load_network_timezone; + iface->load_network_timezone_finish = modem_time_load_network_timezone_finish; +} + +static void +mm_broadband_modem_icera_class_init (MMBroadbandModemIceraClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemIceraPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; + + properties[PROP_DEFAULT_IP_METHOD] = + g_param_spec_enum (MM_BROADBAND_MODEM_ICERA_DEFAULT_IP_METHOD, + "Default IP method", + "Default IP Method (static or DHCP) to use.", + MM_TYPE_BEARER_IP_METHOD, + MM_BEARER_IP_METHOD_STATIC, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_DEFAULT_IP_METHOD, properties[PROP_DEFAULT_IP_METHOD]); +} diff --git a/src/plugins/icera/mm-broadband-modem-icera.h b/src/plugins/icera/mm-broadband-modem-icera.h new file mode 100644 index 00000000..cb88aaf5 --- /dev/null +++ b/src/plugins/icera/mm-broadband-modem-icera.h @@ -0,0 +1,55 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_ICERA_H +#define MM_BROADBAND_MODEM_ICERA_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_ICERA (mm_broadband_modem_icera_get_type ()) +#define MM_BROADBAND_MODEM_ICERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_ICERA, MMBroadbandModemIcera)) +#define MM_BROADBAND_MODEM_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_ICERA, MMBroadbandModemIceraClass)) +#define MM_IS_BROADBAND_MODEM_ICERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_ICERA)) +#define MM_IS_BROADBAND_MODEM_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_ICERA)) +#define MM_BROADBAND_MODEM_ICERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_ICERA, MMBroadbandModemIceraClass)) + +#define MM_BROADBAND_MODEM_ICERA_DEFAULT_IP_METHOD "broadband-modem-icera-default-ip-method" + +typedef struct _MMBroadbandModemIcera MMBroadbandModemIcera; +typedef struct _MMBroadbandModemIceraClass MMBroadbandModemIceraClass; +typedef struct _MMBroadbandModemIceraPrivate MMBroadbandModemIceraPrivate; + +struct _MMBroadbandModemIcera { + MMBroadbandModem parent; + MMBroadbandModemIceraPrivate *priv; +}; + +struct _MMBroadbandModemIceraClass{ + MMBroadbandModemClass parent; +}; + +G_MODULE_EXPORT +GType mm_broadband_modem_icera_get_type (void); + +G_MODULE_EXPORT +MMBroadbandModemIcera *mm_broadband_modem_icera_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_ICERA_H */ diff --git a/src/plugins/icera/mm-modem-helpers-icera.c b/src/plugins/icera/mm-modem-helpers-icera.c new file mode 100644 index 00000000..da1cd873 --- /dev/null +++ b/src/plugins/icera/mm-modem-helpers-icera.c @@ -0,0 +1,389 @@ +/* -*- 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) 2012 Google, Inc. + * Copyright (C) 2012 - 2013 Aleksander Morgado <aleksander@gnu.org> + * Copyright (C) 2014 Dan Williams <dcbw@redhat.com> + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-icera.h" + +/*****************************************************************************/ +/* %IPDPADDR response parser */ + +static MMBearerIpConfig * +parse_ipdpaddr_v4 (const gchar **items, guint num_items, GError **error) +{ + MMBearerIpConfig *config; + const gchar *dns[3] = { 0 }; + guint dns_i = 0, tmp; + const gchar *netmask = NULL; + + /* IP address and prefix */ + tmp = 0; + if (!inet_pton (AF_INET, items[1], &tmp)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse IPv4 address '%s'", items[1]); + return NULL; + } + + if (!tmp) { + /* No IPv4 config */ + return NULL; + } + + config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_STATIC); + mm_bearer_ip_config_set_address (config, items[1]); + mm_bearer_ip_config_set_prefix (config, 32); /* default prefix */ + + /* Gateway */ + tmp = 0; + if (inet_pton (AF_INET, items[2], &tmp)) { + if (tmp) + mm_bearer_ip_config_set_gateway (config, items[2]); + } else { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse gateway address '%s'", items[2]); + goto error; + } + + /* DNS */ + tmp = 0; + if (inet_pton (AF_INET, items[3], &tmp) && tmp) + dns[dns_i++] = items[3]; + else { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse DNS address '%s'", items[3]); + goto error; + } + + /* DNS2 - sometimes missing and set to 0.0.0.0 */ + tmp = 0; + if (inet_pton (AF_INET, items[4], &tmp) && tmp) + dns[dns_i++] = items[4]; + if (dns_i > 0) + mm_bearer_ip_config_set_dns (config, (const gchar **) dns); + + /* Short form (eg, Sierra USB305) */ + if (num_items < 9) + return config; + + /* Devices return netmask and secondary gateway in one of two + * positions. The netmask may be either at index 7 or 8, while + * the secondary gateway may be at position 8 or 9. + */ + + if (items[7] && strstr (items[7], "255.") && !strstr (items[7], "255.0.0.0")) + netmask = items[7]; + if (items[8] && strstr (items[8], "255.") && !strstr (items[8], "255.0.0.0")) + netmask = items[8]; + if (netmask) { + if (!inet_pton (AF_INET, netmask, &tmp)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse netmask '%s'", + netmask); + goto error; + } + mm_bearer_ip_config_set_prefix (config, mm_netmask_to_cidr (netmask)); + } + + /* Secondary gateway */ + if (!mm_bearer_ip_config_get_gateway (config)) { + const char *gw2 = NULL; + + if (num_items >= 10 && items[9] && !strstr (items[9], "255.") && !strstr (items[9], "::")) + gw2 = items[9]; + /* Prefer position 8 */ + if (items[8] && !strstr (items[8], "255.")) + gw2 = items[8]; + + if (gw2 && inet_pton (AF_INET, gw2, &tmp) && tmp) + mm_bearer_ip_config_set_gateway (config, gw2); + else { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse secondary gateway address '%s'", + gw2 ? gw2 : "(unknown)"); + goto error; + } + } + + return config; + +error: + g_object_unref (config); + return NULL; +} + +static MMBearerIpConfig * +parse_ipdpaddr_v6 (const gchar **items, guint num_items, GError **error) +{ + MMBearerIpConfig *config; + const gchar *dns[2] = { 0 }; + struct in6_addr tmp6 = IN6ADDR_ANY_INIT; + + if (num_items < 12) + return NULL; + + /* No IPv6 IP and no IPv6 DNS, return NULL without error. */ + if (g_strcmp0 (items[9], "::") == 0 && g_strcmp0 (items[11], "::") == 0) + return NULL; + + config = mm_bearer_ip_config_new (); + + /* It appears that for IPv6 %IPDPADDR returns only the expected + * link-local address and a DNS address, and that to retrieve the + * default router, extra DNS, and search domains, the host must listen + * for IPv6 Router Advertisements on the net port. + */ + if (g_strcmp0 (items[9], "::") != 0) { + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_STATIC); + /* IP address and prefix */ + if (inet_pton (AF_INET6, items[9], &tmp6) != 1 || + IN6_IS_ADDR_UNSPECIFIED (&tmp6)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse IPv6 address '%s'", items[9]); + goto error; + } + mm_bearer_ip_config_set_address (config, items[9]); + mm_bearer_ip_config_set_prefix (config, 64); + + /* If the address is a link-local one, then SLAAC or DHCP must be used + * to get the real prefix and address. Change the method to DHCP to + * indicate this to clients. + */ + if (IN6_IS_ADDR_LINKLOCAL (&tmp6)) + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP); + } else { + /* No IPv6 given, but DNS will be available, try with DHCP */ + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP); + } + + /* DNS server */ + if (g_strcmp0 (items[11], "::") != 0) { + memset (&tmp6, 0, sizeof (tmp6)); + if (inet_pton (AF_INET6, items[11], &tmp6) == 1 && + !IN6_IS_ADDR_UNSPECIFIED (&tmp6)) { + dns[0] = items[11]; + dns[1] = NULL; + mm_bearer_ip_config_set_dns (config, (const gchar **) dns); + } else { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse DNS address '%s'", items[11]); + goto error; + } + } + + return config; + +error: + g_object_unref (config); + return NULL; +} + +#define IPDPADDR_TAG "%IPDPADDR: " + +gboolean +mm_icera_parse_ipdpaddr_response (const gchar *response, + guint expected_cid, + MMBearerIpConfig **out_ip4_config, + MMBearerIpConfig **out_ip6_config, + GError **error) +{ + MMBearerIpConfig *ip4_config = NULL; + MMBearerIpConfig *ip6_config = NULL; + GError *local = NULL; + gboolean success = FALSE; + char **items; + guint num_items, i; + guint num; + + g_return_val_if_fail (out_ip4_config, FALSE); + g_return_val_if_fail (out_ip6_config, FALSE); + + if (!response || !g_str_has_prefix (response, IPDPADDR_TAG)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing %%IPDPADDR prefix"); + return FALSE; + } + + /* %IPDPADDR: <cid>,<ip>,<gw>,<dns1>,<dns2>[,<nbns1>,<nbns2>[,<??>,<netmask>,<gw>]] + * %IPDPADDR: <cid>,<ip>,<gw>,<dns1>,<dns2>,<nbns1>,<nbns2>,<netmask>,<gw> + * %IPDPADDR: <cid>,<ip>,<gw>,<dns1>,<dns2>,<nbns1>,<nbns2>,<??>,<gw>,<ip6>,::,<ip6_dns1>,::,::,::,::,:: + * + * Sierra USB305: %IPDPADDR: 2, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0 + * K3805-Z: %IPDPADDR: 2, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 255.255.255.0, 21.93.217.10, + * Nokia 21M: %IPDPADDR: 2, 33.196.7.127, 33.196.7.128, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 33.196.7.128, fe80::f:9135:5901, ::, fd00:976a::9, ::, ::, ::, ::, :: + * Nokia 21M: %IPDPADDR: 3, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, fe80::2e:437b:7901, ::, fd00:976a::9, ::, ::, ::, ::, :: + */ + response = mm_strip_tag (response, IPDPADDR_TAG); + items = g_strsplit_set (response, ",", 0); + + /* Strip any spaces on elements; inet_pton() doesn't like them */ + num_items = g_strv_length (items); + for (i = 0; i < num_items; i++) + items[i] = g_strstrip (items[i]); + + if (num_items < 7) { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Malformed IPDPADDR response (not enough items)"); + goto out; + } + + /* Validate context ID */ + if (!mm_get_uint_from_str (items[0], &num) || + num != expected_cid) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unknown CID in IPDPADDR response (got %d, expected %d)", + (guint) num, + expected_cid); + goto out; + } + + ip4_config = parse_ipdpaddr_v4 ((const gchar **) items, num_items, &local); + if (local) { + g_propagate_error (error, local); + goto out; + } + + ip6_config = parse_ipdpaddr_v6 ((const gchar **) items, num_items, &local); + if (local) { + g_propagate_error (error, local); + goto out; + } + + success = TRUE; + +out: + g_strfreev (items); + + *out_ip4_config = ip4_config; + *out_ip6_config = ip6_config; + return success; +} + +/*****************************************************************************/ +/* %IPDPCFG? response parser. + * Modifies the input list of profiles in place + * + * AT%IPDPCFG? + * %IPDPCFG: 1,0,0,,,0 + * %IPDPCFG: 2,0,0,,,0 + * %IPDPCFG: 3,0,2,"user","pass",0 + * %IPDPCFG: 4,0,0,,,0 + * OK + */ + +gboolean +mm_icera_parse_ipdpcfg_query_response (const gchar *str, + GList *profiles, + gpointer log_object, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GError) inner_error = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + guint n_updates = 0; + guint n_profiles; + + n_profiles = g_list_length (profiles); + + r = g_regex_new ("%IPDPCFG:\\s*(\\d+),(\\d+),(\\d+),([^,]*),([^,]*),(\\d+)", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, + 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, str, strlen (str), 0, 0, &match_info, &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + /* Parse the results */ + while (g_match_info_matches (match_info)) { + guint cid; + guint auth; + MMBearerAllowedAuth allowed_auth; + g_autofree gchar *user = NULL; + g_autofree gchar *password = NULL; + GList *l; + + if (!mm_get_uint_from_match_info (match_info, 1, &cid)) { + mm_obj_warn (log_object, "couldn't parse cid from %%IPDPCFG line"); + goto next; + } + + if (!mm_get_uint_from_match_info (match_info, 3, &auth)) { + mm_obj_warn (log_object, "couldn't parse auth from %%IPDPCFG line"); + goto next; + } + + switch (auth) { + case 0: + allowed_auth = MM_BEARER_ALLOWED_AUTH_NONE; + break; + case 1: + allowed_auth = MM_BEARER_ALLOWED_AUTH_PAP; + break; + case 2: + allowed_auth = MM_BEARER_ALLOWED_AUTH_CHAP; + break; + default: + mm_obj_warn (log_object, "unexpected icera auth setting: %u", auth); + goto next; + } + + user = mm_get_string_unquoted_from_match_info (match_info, 4); + password = mm_get_string_unquoted_from_match_info (match_info, 5); + + mm_obj_dbg (log_object, "found icera auth settings for profile with id '%u'", cid); + + /* Find profile and update in place */ + for (l = profiles; l; l = g_list_next (l)) { + MM3gppProfile *iter = l->data; + + if (mm_3gpp_profile_get_profile_id (iter) == (gint) cid) { + n_updates++; + mm_3gpp_profile_set_allowed_auth (iter, allowed_auth); + mm_3gpp_profile_set_user (iter, user); + mm_3gpp_profile_set_password (iter, password); + break; + } + } + if (!l) + mm_obj_warn (log_object, "couldn't update auth settings in profile with id '%d': not found", cid); + + next: + g_match_info_next (match_info, NULL); + } + + if (n_updates != n_profiles) + mm_obj_warn (log_object, "couldn't update auth settings in all profiles: %u/%u updated", + n_updates, n_profiles); + + return TRUE; +} diff --git a/src/plugins/icera/mm-modem-helpers-icera.h b/src/plugins/icera/mm-modem-helpers-icera.h new file mode 100644 index 00000000..fa4f9016 --- /dev/null +++ b/src/plugins/icera/mm-modem-helpers-icera.h @@ -0,0 +1,34 @@ +/* -*- 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) 2014 Dan Williams <dcbw@redhat.com> + */ + +#ifndef MM_MODEM_HELPERS_ICERA_H +#define MM_MODEM_HELPERS_ICERA_H + +#include "glib.h" + +/* %IPDPADDR response parser */ +gboolean mm_icera_parse_ipdpaddr_response (const gchar *response, + guint expected_cid, + MMBearerIpConfig **out_ip4_config, + MMBearerIpConfig **out_ip6_config, + GError **error); + +/* %IPDPCFG? response parser */ +gboolean mm_icera_parse_ipdpcfg_query_response (const gchar *response, + GList *profiles, + gpointer log_object, + GError **error); + +#endif /* MM_MODEM_HELPERS_HUAWEI_H */ diff --git a/src/plugins/icera/mm-shared.c b/src/plugins/icera/mm-shared.c new file mode 100644 index 00000000..735d61bf --- /dev/null +++ b/src/plugins/icera/mm-shared.c @@ -0,0 +1,20 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-shared.h" + +MM_SHARED_DEFINE_MAJOR_VERSION +MM_SHARED_DEFINE_MINOR_VERSION +MM_SHARED_DEFINE_NAME(Icera) diff --git a/src/plugins/icera/tests/test-modem-helpers-icera.c b/src/plugins/icera/tests/test-modem-helpers-icera.c new file mode 100644 index 00000000..dceb1e2b --- /dev/null +++ b/src/plugins/icera/tests/test-modem-helpers-icera.c @@ -0,0 +1,262 @@ +/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org> + * Copyright (C) 2014 Dan Williams <dcbw@redhat.com> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-icera.h" + +/*****************************************************************************/ +/* Test %IPDPADDR responses */ + +typedef struct { + const gchar *str; + const guint expected_cid; + + /* IPv4 */ + const gchar *ipv4_addr; + const guint ipv4_prefix; + const gchar *ipv4_gw; + const gchar *ipv4_dns1; + const gchar *ipv4_dns2; + + /* IPv6 */ + const gchar *ipv6_addr; + const gchar *ipv6_dns1; +} IpdpaddrTest; + +static const IpdpaddrTest ipdpaddr_tests[] = { + /* Sierra USB305 */ + { "%IPDPADDR: 2, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0\r\n", + 2, "21.93.217.11", 32, "21.93.217.10", "10.177.0.34", "10.161.171.220", + NULL, NULL }, + + /* ZTE/Vodafone K3805-Z */ + { "%IPDPADDR: 5, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 255.255.255.0, 21.93.217.10,\r\n", + 5, "21.93.217.11", 24, "21.93.217.10", "10.177.0.34", "10.161.171.220", + NULL, NULL }, + + /* Secondary gateway check */ + { "%IPDPADDR: 5, 21.93.217.11, 0.0.0.0, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 255.255.255.0, 21.93.217.10,\r\n", + 5, "21.93.217.11", 24, "21.93.217.10", "10.177.0.34", "10.161.171.220", + NULL, NULL }, + + /* Secondary gateway check #2 */ + { "%IPDPADDR: 5, 27.107.96.189, 0.0.0.0, 121.242.190.210, 121.242.190.181, 0.0.0.0, 0.0.0.0, 255.255.255.254, 27.107.96.188\r\n", + 5, "27.107.96.189", 31, "27.107.96.188", "121.242.190.210", "121.242.190.181", + NULL, NULL }, + + /* Nokia 21M */ + { "%IPDPADDR: 1, 33.196.7.127, 33.196.7.128, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 33.196.7.128, fe80::f:9135:5901, ::, fd00:976a::9, ::, ::, ::, ::, ::\r\n", + 1, "33.196.7.127", 32, "33.196.7.128", "10.177.0.34", "10.161.171.220", + "fe80::f:9135:5901", "fd00:976a::9" }, + + { "%IPDPADDR: 3, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, fe80::2e:437b:7901, ::, fd00:976a::9, ::, ::, ::, ::, ::\r\n", + 3, NULL, 0, NULL, NULL, NULL, + "fe80::2e:437b:7901", "fd00:976a::9" }, + + /* Some development chip (cnsbg.p1001.rev2, CL477342) */ + { "%IPDPADDR: 5, 27.107.96.189, 27.107.96.188, 121.242.190.210, 121.242.190.181, 0.0.0.0, 0.0.0.0, 255.255.255.254, 27.107.96.188\r\n", + 5, "27.107.96.189", 31, "27.107.96.188", "121.242.190.210", "121.242.190.181", + NULL, NULL }, + + /* 21M with newer firmware */ + { "%IPDPADDR: 2, 188.150.116.13, 188.150.116.14, 188.149.250.16, 0.0.0.0, 0.0.0.0, 0.0.0.0, 255.255.0.0, 188.150.116.14, fe80::1:e414:eb01, ::, 2a00:e18:0:3::6, ::, ::, ::, ::, ::\r\n", + 2, "188.150.116.13", 16, "188.150.116.14", "188.149.250.16", NULL, + "fe80::1:e414:eb01", "2a00:e18:0:3::6" }, + + { "%IPDPADDR: 1, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, fe80::1f:fad1:4c01, ::, 2001:4600:4:fff::54, 2001:4600:4:1fff::54, ::, ::, ::, ::\r\n", + 1, NULL, 0, NULL, NULL, NULL, + "fe80::1f:fad1:4c01", "2001:4600:4:fff::54" }, + + { "%IPDPADDR: 1, 46.157.76.179, 46.157.76.180, 193.213.112.4, 130.67.15.198, 0.0.0.0, 0.0.0.0, 255.0.0.0, 46.157.76.180, ::, ::, ::, ::, ::, ::, ::, ::\r\n", + 1, "46.157.76.179", 32, "46.157.76.180", "193.213.112.4", "130.67.15.198", + NULL, NULL }, + + { "%IPDPADDR: 1, 0.0.0.0, 0.0.0.0, 193.213.112.4, 130.67.15.198, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, ::, ::, 2001:4600:4:fff::52, 2001:4600:4:1fff::52, ::, ::, ::, ::", + 1, NULL, 0, NULL, NULL, NULL, + NULL, "2001:4600:4:fff::52" }, + + { NULL } +}; + +static void +test_ipdpaddr (void) +{ + guint i; + + for (i = 0; ipdpaddr_tests[i].str; i++) { + gboolean success; + GError *error = NULL; + MMBearerIpConfig *ipv4 = NULL; + MMBearerIpConfig *ipv6 = NULL; + const gchar **dns; + guint dnslen; + + success = mm_icera_parse_ipdpaddr_response ( + ipdpaddr_tests[i].str, + ipdpaddr_tests[i].expected_cid, + &ipv4, + &ipv6, + &error); + g_assert_no_error (error); + g_assert (success); + + /* IPv4 */ + if (ipdpaddr_tests[i].ipv4_addr) { + g_assert (ipv4); + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv4), ==, MM_BEARER_IP_METHOD_STATIC); + g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv4), ==, ipdpaddr_tests[i].ipv4_addr); + g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv4), ==, ipdpaddr_tests[i].ipv4_prefix); + g_assert_cmpstr (mm_bearer_ip_config_get_gateway (ipv4), ==, ipdpaddr_tests[i].ipv4_gw); + + dns = mm_bearer_ip_config_get_dns (ipv4); + g_assert (dns); + dnslen = g_strv_length ((gchar **) dns); + if (ipdpaddr_tests[i].ipv4_dns2 != NULL) + g_assert_cmpint (dnslen, ==, 2); + else + g_assert_cmpint (dnslen, ==, 1); + g_assert_cmpstr (dns[0], ==, ipdpaddr_tests[i].ipv4_dns1); + g_assert_cmpstr (dns[1], ==, ipdpaddr_tests[i].ipv4_dns2); + g_object_unref (ipv4); + } else + g_assert (ipv4 == NULL); + + /* IPv6 */ + if (ipdpaddr_tests[i].ipv6_addr || ipdpaddr_tests[i].ipv6_dns1) { + struct in6_addr a6; + g_assert (ipv6); + + if (ipdpaddr_tests[i].ipv6_addr) { + g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv6), ==, ipdpaddr_tests[i].ipv6_addr); + g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv6), ==, 64); + + g_assert (inet_pton (AF_INET6, mm_bearer_ip_config_get_address (ipv6), &a6)); + if (IN6_IS_ADDR_LINKLOCAL (&a6)) + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_DHCP); + else + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_STATIC); + } else + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_DHCP); + + dns = mm_bearer_ip_config_get_dns (ipv6); + g_assert (dns); + dnslen = g_strv_length ((gchar **) dns); + g_assert_cmpint (dnslen, ==, 1); + g_assert_cmpstr (dns[0], ==, ipdpaddr_tests[i].ipv6_dns1); + g_object_unref (ipv6); + } else + g_assert (ipv6 == NULL); + } +} + +/*****************************************************************************/ +/* Test %IPDPCFG responses */ + +static void +test_ipdpcfg (void) +{ + MM3gppProfile *profile; + GList *profiles = NULL; + GList *l; + GError *error = NULL; + gboolean result; + gboolean cid_1_validated = FALSE; + gboolean cid_2_validated = FALSE; + gboolean cid_5_validated = FALSE; + const gchar *response = + "%IPDPCFG: 1,0,0,,,0\r\n" + "%IPDPCFG: 2,0,1,\"aaaa\",\"bbbbb\",0\r\n" + "%IPDPCFG: 5,0,2,\"user\",\"pass\",0"; /* last line without CRLF */ + + profile = mm_3gpp_profile_new (); + mm_3gpp_profile_set_profile_id (profile, 1); + mm_3gpp_profile_set_apn (profile, "internet"); + mm_3gpp_profile_set_ip_type (profile, MM_BEARER_IP_FAMILY_IPV4); + profiles = g_list_append (profiles, profile); + + profile = mm_3gpp_profile_new (); + mm_3gpp_profile_set_profile_id (profile, 2); + mm_3gpp_profile_set_apn (profile, "internet2"); + mm_3gpp_profile_set_ip_type (profile, MM_BEARER_IP_FAMILY_IPV4V6); + profiles = g_list_append (profiles, profile); + + profile = mm_3gpp_profile_new (); + mm_3gpp_profile_set_profile_id (profile, 5); + mm_3gpp_profile_set_apn (profile, "internet3"); + mm_3gpp_profile_set_ip_type (profile, MM_BEARER_IP_FAMILY_IPV6); + profiles = g_list_append (profiles, profile); + + result = mm_icera_parse_ipdpcfg_query_response (response, profiles, NULL, &error); + g_assert_no_error (error); + g_assert (result); + + for (l = profiles; l; l = g_list_next (l)) { + MM3gppProfile *iter = l->data; + + switch (mm_3gpp_profile_get_profile_id (iter)) { + case 1: + cid_1_validated = TRUE; + g_assert_cmpuint (mm_3gpp_profile_get_allowed_auth (iter), ==, MM_BEARER_ALLOWED_AUTH_NONE); + g_assert (!mm_3gpp_profile_get_user (iter)); + g_assert (!mm_3gpp_profile_get_password (iter)); + break; + case 2: + cid_2_validated = TRUE; + g_assert_cmpuint (mm_3gpp_profile_get_allowed_auth (iter), ==, MM_BEARER_ALLOWED_AUTH_PAP); + g_assert_cmpstr (mm_3gpp_profile_get_user (iter), ==, "aaaa"); + g_assert_cmpstr (mm_3gpp_profile_get_password (iter), ==, "bbbbb"); + break; + case 5: + cid_5_validated = TRUE; + g_assert_cmpuint (mm_3gpp_profile_get_allowed_auth (iter), ==, MM_BEARER_ALLOWED_AUTH_CHAP); + g_assert_cmpstr (mm_3gpp_profile_get_user (iter), ==, "user"); + g_assert_cmpstr (mm_3gpp_profile_get_password (iter), ==, "pass"); + break; + default: + g_assert_not_reached (); + } + } + g_assert (cid_1_validated); + g_assert (cid_2_validated); + g_assert (cid_5_validated); + + g_list_free_full (profiles, (GDestroyNotify)g_object_unref); +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/icera/ipdpaddr", test_ipdpaddr); + g_test_add_func ("/MM/icera/ipdpcfg", test_ipdpcfg); + + return g_test_run (); +} diff --git a/src/plugins/intel/mm-broadband-modem-mbim-intel.c b/src/plugins/intel/mm-broadband-modem-mbim-intel.c new file mode 100644 index 00000000..f6cf33db --- /dev/null +++ b/src/plugins/intel/mm-broadband-modem-mbim-intel.c @@ -0,0 +1,144 @@ +/* -*- 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) 2021-2022 Intel Corporation + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <time.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem-mbim-intel.h" +#include "mm-iface-modem-location.h" +#include "mm-shared-xmm.h" + +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void shared_xmm_init (MMSharedXmm *iface); + +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimIntel, mm_broadband_modem_mbim_intel, MM_TYPE_BROADBAND_MODEM_MBIM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_XMM, shared_xmm_init)) + +/*****************************************************************************/ + +static void +setup_ports (MMBroadbandModem *self) +{ + MMPortSerialAt *ports[3]; + guint i; + + /* Run the shared XMM port setup logic */ + mm_shared_xmm_setup_ports (self); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* GNSS control port may or may not be a primary/secondary port */ + ports[2] = mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)); + if (ports[2] && ((ports[2] == ports[0]) || (ports[2] == ports[1]))) + ports[2] = NULL; + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + g_object_set (ports[i], + MM_PORT_SERIAL_SEND_DELAY, (guint64) 0, + NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemMbimIntel * +mm_broadband_modem_mbim_intel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_INTEL, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* MBIM bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE, +#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED + MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED, TRUE, +#endif + NULL); +} + +static void +mm_broadband_modem_mbim_intel_init (MMBroadbandModemMbimIntel *self) +{ +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_xmm_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_xmm_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_xmm_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_xmm_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_xmm_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_xmm_disable_location_gathering_finish; + iface->load_supl_server = mm_shared_xmm_location_load_supl_server; + iface->load_supl_server_finish = mm_shared_xmm_location_load_supl_server_finish; + iface->set_supl_server = mm_shared_xmm_location_set_supl_server; + iface->set_supl_server_finish = mm_shared_xmm_location_set_supl_server_finish; +} + +static MMBroadbandModemClass * +peek_parent_broadband_modem_class (MMSharedXmm *self) +{ + return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbim_intel_parent_class); +} + +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedXmm *self) +{ + return iface_modem_location_parent; +} + +static void +shared_xmm_init (MMSharedXmm *iface) +{ + iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class; + iface->peek_parent_location_interface = peek_parent_location_interface; +} + +static void +mm_broadband_modem_mbim_intel_class_init (MMBroadbandModemMbimIntelClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/intel/mm-broadband-modem-mbim-intel.h b/src/plugins/intel/mm-broadband-modem-mbim-intel.h new file mode 100644 index 00000000..549f179d --- /dev/null +++ b/src/plugins/intel/mm-broadband-modem-mbim-intel.h @@ -0,0 +1,47 @@ +/* -*- 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) 2021-2022 Intel Corporation + */ + +#ifndef MM_BROADBAND_MODEM_MBIM_INTEL_H +#define MM_BROADBAND_MODEM_MBIM_INTEL_H + +#include "mm-broadband-modem-mbim.h" + +#define MM_TYPE_BROADBAND_MODEM_MBIM_INTEL (mm_broadband_modem_mbim_intel_get_type ()) +#define MM_BROADBAND_MODEM_MBIM_INTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_INTEL, MMBroadbandModemMbimIntel)) +#define MM_BROADBAND_MODEM_MBIM_INTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_INTEL, MMBroadbandModemMbimIntelClass)) +#define MM_IS_BROADBAND_MODEM_MBIM_INTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_INTEL)) +#define MM_IS_BROADBAND_MODEM_MBIM_INTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_INTEL)) +#define MM_BROADBAND_MODEM_MBIM_INTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_INTEL, MMBroadbandModemMbimIntelClass)) + +typedef struct _MMBroadbandModemMbimIntel MMBroadbandModemMbimIntel; +typedef struct _MMBroadbandModemMbimIntelClass MMBroadbandModemMbimIntelClass; + +struct _MMBroadbandModemMbimIntel { + MMBroadbandModemMbim parent; +}; + +struct _MMBroadbandModemMbimIntelClass{ + MMBroadbandModemMbimClass parent; +}; + +GType mm_broadband_modem_mbim_intel_get_type (void); + +MMBroadbandModemMbimIntel *mm_broadband_modem_mbim_intel_new (const gchar *device, + const gchar **driver, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_MBIM_INTEL_H */ diff --git a/src/plugins/intel/mm-plugin-intel.c b/src/plugins/intel/mm-plugin-intel.c new file mode 100644 index 00000000..d83edfba --- /dev/null +++ b/src/plugins/intel/mm-plugin-intel.c @@ -0,0 +1,91 @@ +/* -*- 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) 2021-2022 Intel Corporation + */ + +#include <stdio.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-intel.h" +#include "mm-log-object.h" +#include "mm-broadband-modem.h" + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim-intel.h" +#endif + +G_DEFINE_TYPE (MMPluginIntel, mm_plugin_intel, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered Intel modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_intel_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + mm_obj_dbg (self, "Generic Intel modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "net", "wwan", NULL }; + static const guint16 vendor_ids[] = { 0x8086, 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_INTEL, + MM_PLUGIN_NAME, "Intel", + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + NULL)); +} + +static void +mm_plugin_intel_init (MMPluginIntel *self) +{ + /*nothing to be done here, but required for creating intel plugin instance*/ +} + +static void +mm_plugin_intel_class_init (MMPluginIntelClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/intel/mm-plugin-intel.h b/src/plugins/intel/mm-plugin-intel.h new file mode 100644 index 00000000..3d2880b2 --- /dev/null +++ b/src/plugins/intel/mm-plugin-intel.h @@ -0,0 +1,40 @@ +/* -*- 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) 2021-2022 Intel Corporation + */ + +#ifndef MM_PLUGIN_INTEL_H +#define MM_PLUGIN_INTEL_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_INTEL (mm_plugin_intel_get_type ()) +#define MM_PLUGIN_INTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_INTEL, MMPluginIntel)) +#define MM_PLUGIN_INTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_INTEL, MMPluginIntelClass)) +#define MM_IS_PLUGIN_INTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_INTEL)) +#define MM_IS_PLUGIN_INTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_INTEL)) +#define MM_PLUGIN_INTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_INTEL, MMPluginIntelClass)) + +typedef struct { + MMPlugin parent; +} MMPluginIntel; + +typedef struct { + MMPluginClass parent; +} MMPluginIntelClass; + +GType mm_plugin_intel_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_INTEL_H */ diff --git a/src/plugins/iridium/mm-bearer-iridium.c b/src/plugins/iridium/mm-bearer-iridium.c new file mode 100644 index 00000000..52e8ada9 --- /dev/null +++ b/src/plugins/iridium/mm-bearer-iridium.c @@ -0,0 +1,266 @@ +/* -*- 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) 2012 Ammonit Measurement GmbH + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-bearer-iridium.h" +#include "mm-base-modem-at.h" + +/* Allow up to 200s to get a proper IP connection */ +#define BEARER_IRIDIUM_IP_TIMEOUT_DEFAULT 200 + +G_DEFINE_TYPE (MMBearerIridium, mm_bearer_iridium, MM_TYPE_BASE_BEARER) + +/*****************************************************************************/ +/* Connect */ + +typedef struct { + MMPortSerialAt *primary; + GError *saved_error; +} ConnectContext; + +static void +connect_context_free (ConnectContext *ctx) +{ + if (ctx->saved_error) + g_error_free (ctx->saved_error); + if (ctx->primary) + g_object_unref (ctx->primary); + g_free (ctx); +} + +static MMBearerConnectResult * +connect_finish (MMBaseBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +connect_report_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + ConnectContext *ctx; + const gchar *result; + + /* If cancelled, complete */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + /* If we got a proper extended reply, build the new error to be set */ + result = mm_base_modem_at_command_full_finish (modem, res, NULL); + if (result && g_str_has_prefix (result, "+CEER: ") && strlen (result) > 7) { + g_task_return_new_error (task, + ctx->saved_error->domain, + ctx->saved_error->code, + "%s", &result[7]); + } else { + /* Otherwise, take the original error as it was */ + g_task_return_error (task, ctx->saved_error); + ctx->saved_error = NULL; + } + g_object_unref (task); +} + +static void +dial_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + ConnectContext *ctx; + MMBearerIpConfig *config; + + ctx = g_task_get_task_data (task); + + /* DO NOT check for cancellable here. If we got here without errors, the + * bearer is really connected and therefore we need to reflect that in + * the state machine. */ + mm_base_modem_at_command_full_finish (modem, res, &(ctx->saved_error)); + if (ctx->saved_error) { + /* Try to get more information why it failed */ + mm_base_modem_at_command_full ( + modem, + ctx->primary, + "+CEER", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)connect_report_ready, + task); + return; + } + + /* Port is connected; update the state */ + mm_port_set_connected (MM_PORT (ctx->primary), TRUE); + + /* Build IP config; always PPP based */ + config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_PPP); + + /* Return operation result */ + g_task_return_pointer ( + task, + mm_bearer_connect_result_new (MM_PORT (ctx->primary), config, NULL), + (GDestroyNotify)mm_bearer_connect_result_unref); + g_object_unref (task); + g_object_unref (config); +} + +static void +service_type_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + ConnectContext *ctx; + GError *error = NULL; + + /* If cancelled, complete */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + /* Errors setting the service type will be critical */ + mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* We just use the default number to dial in the Iridium network. Also note + * that we won't specify a specific port to use; Iridium modems only expose + * one. */ + mm_base_modem_at_command_full ( + modem, + ctx->primary, + "ATDT008816000025", + MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)dial_ready, + task); +} + +static void +connect (MMBaseBearer *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ConnectContext *ctx; + GTask *task; + MMBaseModem *modem = NULL; + + task = g_task_new (self, cancellable, callback, user_data); + + if (mm_bearer_properties_get_multiplex (mm_base_bearer_peek_config (self)) == MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Multiplex support not available"); + g_object_unref (task); + return; + } + + g_object_get (self, + MM_BASE_BEARER_MODEM, &modem, + NULL); + g_assert (modem); + + /* Don't bother to get primary and check if connected and all that; we + * already do this check when sending the ATDT call */ + + /* In this context, we only keep the stuff we'll need later */ + ctx = g_new0 (ConnectContext, 1); + ctx->primary = mm_base_modem_get_port_primary (modem); + g_task_set_task_data (task, ctx, (GDestroyNotify) connect_context_free); + + /* Bearer service type set to 9600bps (V.110), which behaves better than the + * default 9600bps (V.32). */ + mm_base_modem_at_command_full ( + modem, + ctx->primary, + "+CBST=71,0,1", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)service_type_ready, + task); + + g_object_unref (modem); +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_bearer_iridium_new (MMBroadbandModemIridium *modem, + MMBearerProperties *config) +{ + MMBaseBearer *bearer; + + /* The Iridium bearer inherits from MMBaseBearer (so it's not a MMBroadbandBearer) + * and that means that the object is not async-initable, so we just use + * g_object_get() here */ + bearer = g_object_new (MM_TYPE_BEARER_IRIDIUM, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + "ip-timeout", BEARER_IRIDIUM_IP_TIMEOUT_DEFAULT, + NULL); + + /* Only export valid bearers */ + mm_base_bearer_export (bearer); + + return bearer; +} + +static void +mm_bearer_iridium_init (MMBearerIridium *self) +{ +} + +static void +mm_bearer_iridium_class_init (MMBearerIridiumClass *klass) +{ + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + + /* Virtual methods */ + base_bearer_class->connect = connect; + base_bearer_class->connect_finish = connect_finish; + base_bearer_class->load_connection_status = NULL; + base_bearer_class->load_connection_status_finish = NULL; +#if defined WITH_SUSPEND_RESUME + base_bearer_class->reload_connection_status = NULL; + base_bearer_class->reload_connection_status_finish = NULL; +#endif +} diff --git a/src/plugins/iridium/mm-bearer-iridium.h b/src/plugins/iridium/mm-bearer-iridium.h new file mode 100644 index 00000000..eba6ac54 --- /dev/null +++ b/src/plugins/iridium/mm-bearer-iridium.h @@ -0,0 +1,55 @@ +/* -*- 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: + * + * Author: Aleksander Morgado <aleksander@lanedo.com> + * + * Copyright (C) 2012 Ammonit Measurement GmbH. + */ + +#ifndef MM_BEARER_IRIDIUM_H +#define MM_BEARER_IRIDIUM_H + +#include <glib.h> +#include <glib-object.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-bearer.h" +#include "mm-broadband-modem-iridium.h" + +#define MM_TYPE_BEARER_IRIDIUM (mm_bearer_iridium_get_type ()) +#define MM_BEARER_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BEARER_IRIDIUM, MMBearerIridium)) +#define MM_BEARER_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BEARER_IRIDIUM, MMBearerIridiumClass)) +#define MM_IS_BEARER_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BEARER_IRIDIUM)) +#define MM_IS_BEARER_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BEARER_IRIDIUM)) +#define MM_BEARER_IRIDIUM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BEARER_IRIDIUM, MMBearerIridiumClass)) + +typedef struct _MMBearerIridium MMBearerIridium; +typedef struct _MMBearerIridiumClass MMBearerIridiumClass; + +struct _MMBearerIridium { + MMBaseBearer parent; +}; + +struct _MMBearerIridiumClass { + MMBaseBearerClass parent; +}; + +GType mm_bearer_iridium_get_type (void); + +/* Iridium bearer creation implementation. + * NOTE it is *not* a broadband bearer, so not async-initable */ +MMBaseBearer *mm_bearer_iridium_new (MMBroadbandModemIridium *modem, + MMBearerProperties *config); + +#endif /* MM_BEARER_IRIDIUM_H */ diff --git a/src/plugins/iridium/mm-broadband-modem-iridium.c b/src/plugins/iridium/mm-broadband-modem-iridium.c new file mode 100644 index 00000000..681d9123 --- /dev/null +++ b/src/plugins/iridium/mm-broadband-modem-iridium.c @@ -0,0 +1,433 @@ +/* -*- 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 - 2012 Ammonit Measurement GmbH + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-errors-types.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-messaging.h" +#include "mm-broadband-modem-iridium.h" +#include "mm-sim-iridium.h" +#include "mm-bearer-iridium.h" +#include "mm-modem-helpers.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_messaging_init (MMIfaceModemMessaging *iface); + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemIridium, mm_broadband_modem_iridium, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)) + +/*****************************************************************************/ +/* Operator Code loading (3GPP interface) */ + +static gchar * +load_operator_code_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_operator_code (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + /* Only "90103" operator code is assumed */ + g_task_return_pointer (task, g_strdup ("90103"), g_free); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Operator Name loading (3GPP interface) */ + +static gchar * +load_operator_name_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_operator_name (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + /* Only "IRIDIUM" operator name is assumed */ + g_task_return_pointer (task, g_strdup ("IRIDIUM"), g_free); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Enable unsolicited events (SMS indications) (Messaging interface) */ + +static gboolean +messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +messaging_enable_unsolicited_events (MMIfaceModemMessaging *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* AT+CNMI=<mode>,[<mt>[,<bm>[,<ds>[,<bfr>]]]] + * but <bm> can only be 0, + * and <ds> can only be either 0 or 1 + * + * Note: Modem may return +CMS ERROR:322, which indicates Memory Full, + * not a big deal + */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CNMI=2,1,0,0,1", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Signal quality (Modem interface) */ + +static guint +load_signal_quality_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + gint quality = 0; + const gchar *result; + + result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!result) + return 0; + + /* Skip possible whitespaces after '+CSQF:' and before the response */ + result = mm_strip_tag (result, "+CSQF:"); + while (*result == ' ') + result++; + + if (sscanf (result, "%d", &quality)) + /* Normalize the quality. <rssi> is NOT given in dBs, + * given as a relative value between 0 and 5 */ + quality = CLAMP (quality, 0, 5) * 100 / 5; + else + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Could not parse signal quality results"); + + return quality; +} + +static void +load_signal_quality (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* The iridium modem may have a huge delay to get signal quality if we pass + * AT+CSQ, so we'll default to use AT+CSQF, which is a fast version that + * returns right away the last signal quality value retrieved */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CSQF", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Flow control (Modem interface) */ + +static gboolean +setup_flow_control_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +setup_flow_control_ready (MMBroadbandModemIridium *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) + /* Let the error be critical. We DO need RTS/CTS in order to have + * proper modem disabling. */ + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +setup_flow_control (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Enable RTS/CTS flow control. + * Other available values: + * AT&K0: Disable flow control + * AT&K3: RTS/CTS + * AT&K4: XOFF/XON + * AT&K6: Both RTS/CTS and XOFF/XON + */ + g_object_set (self, MM_BROADBAND_MODEM_FLOW_CONTROL, MM_FLOW_CONTROL_RTS_CTS, NULL); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "&K3", + 3, + FALSE, + (GAsyncReadyCallback)setup_flow_control_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load supported modes (Modem inteface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GArray *combinations; + MMModemModeCombination mode; + GTask *task; + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1); + + /* Report CS only, Iridium connections are circuit-switched */ + mode.allowed = MM_MODEM_MODE_CS; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + task = g_task_new (self, NULL, callback, user_data); + g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Create SIM (Modem inteface) */ + +static MMBaseSim * +create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_iridium_new_finish (res, error); +} + +static void +create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* New Iridium SIM */ + mm_sim_iridium_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBaseBearer *bearer; + GTask *task; + + mm_obj_dbg (self, "creating Iridium bearer..."); + bearer = mm_bearer_iridium_new (MM_BROADBAND_MODEM_IRIDIUM (self), + properties); + task = g_task_new (self, NULL, callback, user_data); + g_task_return_pointer (task, bearer, g_object_unref); + g_object_unref (task); +} + +/*****************************************************************************/ + +static const gchar *primary_init_sequence[] = { + /* Disable echo */ + "E0", + /* Get word responses */ + "V1", + /* Extended numeric codes */ + "+CMEE=1", + NULL +}; + +static void +setup_ports (MMBroadbandModem *self) +{ + MMPortSerialAt *primary; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_iridium_parent_class)->setup_ports (self); + + /* Set 9600 baudrate by default in the AT port */ + mm_obj_dbg (self, "baudrate will be set to 9600 bps..."); + primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + if (!primary) + return; + + g_object_set (G_OBJECT (primary), + MM_PORT_SERIAL_BAUD, 9600, + MM_PORT_SERIAL_AT_INIT_SEQUENCE, primary_init_sequence, + NULL); +} + +/*****************************************************************************/ + +MMBroadbandModemIridium * +mm_broadband_modem_iridium_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_IRIDIUM, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Iridium bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + /* Allow only up to 3 consecutive timeouts in the serial port */ + MM_BASE_MODEM_MAX_TIMEOUTS, 3, + /* Only CS network is supported by the Iridium modem */ + MM_IFACE_MODEM_3GPP_PS_NETWORK_SUPPORTED, FALSE, + NULL); +} + +static void +mm_broadband_modem_iridium_init (MMBroadbandModemIridium *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + /* Create Iridium-specific SIM and bearer*/ + iface->create_sim = create_sim; + iface->create_sim_finish = create_sim_finish; + iface->create_bearer = create_bearer; + iface->create_bearer_finish = create_bearer_finish; + + /* CSQF-based signal quality */ + iface->load_signal_quality = load_signal_quality; + iface->load_signal_quality_finish = load_signal_quality_finish; + + /* RTS/CTS flow control */ + iface->setup_flow_control = setup_flow_control; + iface->setup_flow_control_finish = setup_flow_control_finish; + + /* No need to power-up/power-down the modem */ + iface->load_power_state = NULL; + iface->load_power_state_finish = NULL; + iface->modem_power_up = NULL; + iface->modem_power_up_finish = NULL; + iface->modem_power_down = NULL; + iface->modem_power_down_finish = NULL; + + /* Supported modes cannot be queried */ + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + /* Fixed operator code and name to be reported */ + iface->load_operator_name = load_operator_name; + iface->load_operator_name_finish = load_operator_name_finish; + iface->load_operator_code = load_operator_code; + iface->load_operator_code_finish = load_operator_code_finish; + + /* Don't try to scan networks with AT+COPS=?. + * It does work, but it will only reply about the Iridium network + * being found (so not very helpful, as that is the only one expected), but + * also, it will use a non-standard reply format. Instead of supporting that + * specific format used, just fully skip it. + * For reference, the result is: + * +COPS:(002),"IRIDIUM","IRIDIUM","90103",,(000-001),(000-002) + */ + iface->scan_networks = NULL; + iface->scan_networks_finish = NULL; +} + +static void +iface_modem_messaging_init (MMIfaceModemMessaging *iface) +{ + iface->enable_unsolicited_events = messaging_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = messaging_enable_unsolicited_events_finish; +} + +static void +mm_broadband_modem_iridium_class_init (MMBroadbandModemIridiumClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/iridium/mm-broadband-modem-iridium.h b/src/plugins/iridium/mm-broadband-modem-iridium.h new file mode 100644 index 00000000..b9a1270b --- /dev/null +++ b/src/plugins/iridium/mm-broadband-modem-iridium.h @@ -0,0 +1,49 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2011 Red Hat, Inc. + * Copyright (C) 2011 Google Inc. + */ + +#ifndef MM_BROADBAND_MODEM_IRIDIUM_H +#define MM_BROADBAND_MODEM_IRIDIUM_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_IRIDIUM (mm_broadband_modem_iridium_get_type ()) +#define MM_BROADBAND_MODEM_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_IRIDIUM, MMBroadbandModemIridium)) +#define MM_BROADBAND_MODEM_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_IRIDIUM, MMBroadbandModemIridiumClass)) +#define MM_IS_BROADBAND_MODEM_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_IRIDIUM)) +#define MM_IS_BROADBAND_MODEM_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_IRIDIUM)) +#define MM_BROADBAND_MODEM_IRIDIUM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_IRIDIUM, MMBroadbandModemIridiumClass)) + +typedef struct _MMBroadbandModemIridium MMBroadbandModemIridium; +typedef struct _MMBroadbandModemIridiumClass MMBroadbandModemIridiumClass; + +struct _MMBroadbandModemIridium { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemIridiumClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_iridium_get_type (void); + +MMBroadbandModemIridium *mm_broadband_modem_iridium_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_IRIDIUM_H */ diff --git a/src/plugins/iridium/mm-plugin-iridium.c b/src/plugins/iridium/mm-plugin-iridium.c new file mode 100644 index 00000000..741847f8 --- /dev/null +++ b/src/plugins/iridium/mm-plugin-iridium.c @@ -0,0 +1,89 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-iridium.h" +#include "mm-broadband-modem-iridium.h" +#include "mm-private-boxed-types.h" + +G_DEFINE_TYPE (MMPluginIridium, mm_plugin_iridium, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_iridium_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + static const guint16 vendor_ids[] = { 0x1edd, 0 }; + static const gchar *vendor_strings[] = { "iridium", NULL }; + /* Also support motorola-branded Iridium modems */ + static const mm_str_pair product_strings[] = {{(gchar *)"motorola", (gchar *)"satellite" }, + { NULL, NULL }}; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_IRIDIUM, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings, + MM_PLUGIN_ALLOWED_PRODUCT_STRINGS, product_strings, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + NULL)); +} + +static void +mm_plugin_iridium_init (MMPluginIridium *self) +{ +} + +static void +mm_plugin_iridium_class_init (MMPluginIridiumClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/iridium/mm-plugin-iridium.h b/src/plugins/iridium/mm-plugin-iridium.h new file mode 100644 index 00000000..b729ce98 --- /dev/null +++ b/src/plugins/iridium/mm-plugin-iridium.h @@ -0,0 +1,47 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#ifndef MM_PLUGIN_IRIDIUM_H +#define MM_PLUGIN_IRIDIUM_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_IRIDIUM (mm_plugin_iridium_get_type ()) +#define MM_PLUGIN_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_IRIDIUM, MMPluginIridium)) +#define MM_PLUGIN_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_IRIDIUM, MMPluginIridiumClass)) +#define MM_IS_PLUGIN_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_IRIDIUM)) +#define MM_IS_PLUGIN_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_IRIDIUM)) +#define MM_PLUGIN_IRIDIUM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_IRIDIUM, MMPluginIridiumClass)) + +typedef struct { + MMPlugin parent; +} MMPluginIridium; + +typedef struct { + MMPluginClass parent; +} MMPluginIridiumClass; + +GType mm_plugin_iridium_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_IRIDIUM_H */ diff --git a/src/plugins/iridium/mm-sim-iridium.c b/src/plugins/iridium/mm-sim-iridium.c new file mode 100644 index 00000000..3495039b --- /dev/null +++ b/src/plugins/iridium/mm-sim-iridium.c @@ -0,0 +1,95 @@ +/* -*- 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 - 2012 Ammonit Measurement GmbH. + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-sim-iridium.h" + +G_DEFINE_TYPE (MMSimIridium, mm_sim_iridium, MM_TYPE_BASE_SIM) + +/*****************************************************************************/ + +MMBaseSim * +mm_sim_iridium_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *sim; + + source = g_async_result_get_source_object (res); + sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!sim) + return NULL; + + /* Only export valid SIMs */ + mm_base_sim_export (MM_BASE_SIM (sim)); + + return MM_BASE_SIM (sim); +} + +void +mm_sim_iridium_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (MM_TYPE_SIM_IRIDIUM, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_SIM_MODEM, modem, + "active", TRUE, /* by default always active */ + NULL); +} + +static void +mm_sim_iridium_init (MMSimIridium *self) +{ +} + +static void +mm_sim_iridium_class_init (MMSimIridiumClass *klass) +{ + MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass); + + /* Skip querying the SIM card info, not supported by Iridium modems */ + base_sim_class->load_sim_identifier = NULL; + base_sim_class->load_sim_identifier_finish = NULL; + base_sim_class->load_imsi = NULL; + base_sim_class->load_imsi_finish = NULL; + base_sim_class->load_operator_identifier = NULL; + base_sim_class->load_operator_identifier_finish = NULL; + base_sim_class->load_operator_name = NULL; + base_sim_class->load_operator_name_finish = NULL; + + /* Skip managing preferred networks, not applicable to Iridium modems */ + base_sim_class->load_preferred_networks = NULL; + base_sim_class->load_preferred_networks_finish = NULL; + base_sim_class->set_preferred_networks = NULL; + base_sim_class->set_preferred_networks_finish = NULL; +} diff --git a/src/plugins/iridium/mm-sim-iridium.h b/src/plugins/iridium/mm-sim-iridium.h new file mode 100644 index 00000000..2f3e2916 --- /dev/null +++ b/src/plugins/iridium/mm-sim-iridium.h @@ -0,0 +1,52 @@ +/* -*- 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 - 2012 Ammonit Measurement GmbH. + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#ifndef MM_SIM_IRIDIUM_H +#define MM_SIM_IRIDIUM_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-sim.h" + +#define MM_TYPE_SIM_IRIDIUM (mm_sim_iridium_get_type ()) +#define MM_SIM_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_IRIDIUM, MMSimIridium)) +#define MM_SIM_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_IRIDIUM, MMSimIridiumClass)) +#define MM_IS_SIM_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_IRIDIUM)) +#define MM_IS_SIM_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_IRIDIUM)) +#define MM_SIM_IRIDIUM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_IRIDIUM, MMSimIridiumClass)) + +typedef struct _MMSimIridium MMSimIridium; +typedef struct _MMSimIridiumClass MMSimIridiumClass; + +struct _MMSimIridium { + MMBaseSim parent; +}; + +struct _MMSimIridiumClass { + MMBaseSimClass parent; +}; + +GType mm_sim_iridium_get_type (void); + +void mm_sim_iridium_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_sim_iridium_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_SIM_IRIDIUM_H */ diff --git a/src/plugins/linktop/77-mm-linktop-port-types.rules b/src/plugins/linktop/77-mm-linktop-port-types.rules new file mode 100644 index 00000000..dc2ef0d6 --- /dev/null +++ b/src/plugins/linktop/77-mm-linktop-port-types.rules @@ -0,0 +1,16 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_linktop_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="230d", GOTO="mm_linktop_generic" +GOTO="mm_linktop_end" + +LABEL="mm_linktop_generic" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Linktop HSPADataCard +# ttyACM0 (if #1): Data port +# ttyACM1 (if #3): Primary AT port +ATTRS{idVendor}=="230d", ATTRS{idProduct}=="0001", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1" +ATTRS{idVendor}=="230d", ATTRS{idProduct}=="0001", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +LABEL="mm_linktop_end" diff --git a/src/plugins/linktop/mm-broadband-modem-linktop.c b/src/plugins/linktop/mm-broadband-modem-linktop.c new file mode 100644 index 00000000..a83682c8 --- /dev/null +++ b/src/plugins/linktop/mm-broadband-modem-linktop.c @@ -0,0 +1,269 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "ModemManager.h" +#include "mm-serial-parsers.h" +#include "mm-modem-helpers.h" +#include "mm-iface-modem.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-linktop.h" +#include "mm-modem-helpers-linktop.h" + +static void iface_modem_init (MMIfaceModem *iface); + +static MMIfaceModem *iface_modem_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemLinktop, mm_broadband_modem_linktop, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)) + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMModemModeCombination mode; + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + const gchar *response; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response || !mm_linktop_parse_cfun_query_current_modes (response, allowed, error)) + return FALSE; + + /* None preferred always */ + *preferred = MM_MODEM_MODE_NONE; + + return TRUE; +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +allowed_mode_update_ready (MMBroadbandModemLinktop *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (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); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *command; + gint linktop_mode = -1; + + task = g_task_new (self, NULL, callback, user_data); + + if (allowed == MM_MODEM_MODE_2G) + linktop_mode = LINKTOP_MODE_2G; + else if (allowed == MM_MODEM_MODE_3G) + linktop_mode = LINKTOP_MODE_3G; + else if ((allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) && + (preferred == MM_MODEM_MODE_NONE)) + linktop_mode = LINKTOP_MODE_ANY; + else if ((allowed == MM_MODEM_MODE_ANY && + preferred == MM_MODEM_MODE_NONE)) + linktop_mode = LINKTOP_MODE_ANY; + + if (linktop_mode < 0) { + 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_object_unref (task); + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("AT+CFUN=%d", linktop_mode); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)allowed_mode_update_ready, + task); + g_free (command); +} + +/*****************************************************************************/ + +MMBroadbandModemLinktop * +mm_broadband_modem_linktop_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_LINKTOP, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports AT only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_linktop_init (MMBroadbandModemLinktop *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; +} + +static void +mm_broadband_modem_linktop_class_init (MMBroadbandModemLinktopClass *klass) +{ +} diff --git a/src/plugins/linktop/mm-broadband-modem-linktop.h b/src/plugins/linktop/mm-broadband-modem-linktop.h new file mode 100644 index 00000000..385a20b8 --- /dev/null +++ b/src/plugins/linktop/mm-broadband-modem-linktop.h @@ -0,0 +1,49 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_LINKTOP_H +#define MM_BROADBAND_MODEM_LINKTOP_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_LINKTOP (mm_broadband_modem_linktop_get_type ()) +#define MM_BROADBAND_MODEM_LINKTOP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_LINKTOP, MMBroadbandModemLinktop)) +#define MM_BROADBAND_MODEM_LINKTOP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_LINKTOP, MMBroadbandModemLinktopClass)) +#define MM_IS_BROADBAND_MODEM_LINKTOP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_LINKTOP)) +#define MM_IS_BROADBAND_MODEM_LINKTOP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_LINKTOP)) +#define MM_BROADBAND_MODEM_LINKTOP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_LINKTOP, MMBroadbandModemLinktopClass)) + +typedef struct _MMBroadbandModemLinktop MMBroadbandModemLinktop; +typedef struct _MMBroadbandModemLinktopClass MMBroadbandModemLinktopClass; + +struct _MMBroadbandModemLinktop { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemLinktopClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_linktop_get_type (void); + +MMBroadbandModemLinktop *mm_broadband_modem_linktop_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_LINKTOP_H */ diff --git a/src/plugins/linktop/mm-modem-helpers-linktop.c b/src/plugins/linktop/mm-modem-helpers-linktop.c new file mode 100644 index 00000000..2ca46bb6 --- /dev/null +++ b/src/plugins/linktop/mm-modem-helpers-linktop.c @@ -0,0 +1,54 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2016 Red Hat, Inc. + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-linktop.h" + +/*****************************************************************************/ + +gboolean +mm_linktop_parse_cfun_query_current_modes (const gchar *response, + MMModemMode *allowed, + GError **error) +{ + guint state; + + g_assert (allowed); + + if (!mm_3gpp_parse_cfun_query_response (response, &state, error)) + return FALSE; + + switch (state) { + case LINKTOP_MODE_OFFLINE: + case LINKTOP_MODE_LOW_POWER: + *allowed = MM_MODEM_MODE_NONE; + return TRUE; + case LINKTOP_MODE_2G: + *allowed = MM_MODEM_MODE_2G; + return TRUE; + case LINKTOP_MODE_3G: + *allowed = MM_MODEM_MODE_3G; + return TRUE; + case LINKTOP_MODE_ANY: + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + return TRUE; + default: + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown linktop +CFUN current mode: %u", state); + return FALSE; + } +} diff --git a/src/plugins/linktop/mm-modem-helpers-linktop.h b/src/plugins/linktop/mm-modem-helpers-linktop.h new file mode 100644 index 00000000..69fa7ee2 --- /dev/null +++ b/src/plugins/linktop/mm-modem-helpers-linktop.h @@ -0,0 +1,40 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2016 Red Hat, Inc. + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_MODEM_HELPERS_LINKTOP_H +#define MM_MODEM_HELPERS_LINKTOP_H + +#include <glib.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +typedef enum { + LINKTOP_MODE_OFFLINE = 0, + LINKTOP_MODE_ANY = 1, + LINKTOP_MODE_LOW_POWER = 4, + LINKTOP_MODE_2G = 5, + LINKTOP_MODE_3G = 6, +} MMLinktopMode; + +/* AT+CFUN? response parsers */ +gboolean mm_linktop_parse_cfun_query_current_modes (const gchar *response, + MMModemMode *allowed, + GError **error); + +#endif /* MM_MODEM_HELPERS_LINKTOP_H */ diff --git a/src/plugins/linktop/mm-plugin-linktop.c b/src/plugins/linktop/mm-plugin-linktop.c new file mode 100644 index 00000000..8276e59f --- /dev/null +++ b/src/plugins/linktop/mm-plugin-linktop.c @@ -0,0 +1,79 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-linktop.h" +#include "mm-broadband-modem-linktop.h" + +G_DEFINE_TYPE (MMPluginLinktop, mm_plugin_linktop, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_linktop_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + static const guint16 vendor_ids[] = { 0x230d, 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_LINKTOP, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + NULL)); +} + +static void +mm_plugin_linktop_init (MMPluginLinktop *self) +{ +} + +static void +mm_plugin_linktop_class_init (MMPluginLinktopClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/linktop/mm-plugin-linktop.h b/src/plugins/linktop/mm-plugin-linktop.h new file mode 100644 index 00000000..6c8e5789 --- /dev/null +++ b/src/plugins/linktop/mm-plugin-linktop.h @@ -0,0 +1,42 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_LINKTOP_H +#define MM_PLUGIN_LINKTOP_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_LINKTOP (mm_plugin_linktop_get_type ()) +#define MM_PLUGIN_LINKTOP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_LINKTOP, MMPluginLinktop)) +#define MM_PLUGIN_LINKTOP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_LINKTOP, MMPluginLinktopClass)) +#define MM_IS_PLUGIN_LINKTOP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_LINKTOP)) +#define MM_IS_PLUGIN_LINKTOP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_LINKTOP)) +#define MM_PLUGIN_LINKTOP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_LINKTOP, MMPluginLinktopClass)) + +typedef struct { + MMPlugin parent; +} MMPluginLinktop; + +typedef struct { + MMPluginClass parent; +} MMPluginLinktopClass; + +GType mm_plugin_linktop_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_LINKTOP_H */ diff --git a/src/plugins/linktop/tests/test-modem-helpers-linktop.c b/src/plugins/linktop/tests/test-modem-helpers-linktop.c new file mode 100644 index 00000000..07aa8378 --- /dev/null +++ b/src/plugins/linktop/tests/test-modem-helpers-linktop.c @@ -0,0 +1,71 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-linktop.h" + +/*****************************************************************************/ + +typedef struct { + const gchar *str; + MMModemMode allowed; +} CfunQueryCurrentModeTest; + +static const CfunQueryCurrentModeTest cfun_query_current_mode_tests[] = { + { "+CFUN: 0", MM_MODEM_MODE_NONE }, + { "+CFUN: 1", MM_MODEM_MODE_2G | MM_MODEM_MODE_3G }, + { "+CFUN: 4", MM_MODEM_MODE_NONE }, + { "+CFUN: 5", MM_MODEM_MODE_2G }, + { "+CFUN: 6", MM_MODEM_MODE_3G }, +}; + +static void +test_cfun_query_current_modes (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (cfun_query_current_mode_tests); i++) { + GError *error = NULL; + gboolean success; + MMModemMode allowed = MM_MODEM_MODE_NONE; + + success = mm_linktop_parse_cfun_query_current_modes (cfun_query_current_mode_tests[i].str, &allowed, &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (cfun_query_current_mode_tests[i].allowed, ==, allowed); + } +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/linktop/cfun/query/current-modes", test_cfun_query_current_modes); + + return g_test_run (); +} diff --git a/src/plugins/longcheer/77-mm-longcheer-port-types.rules b/src/plugins/longcheer/77-mm-longcheer-port-types.rules new file mode 100644 index 00000000..e0fbe849 --- /dev/null +++ b/src/plugins/longcheer/77-mm-longcheer-port-types.rules @@ -0,0 +1,173 @@ +# do not edit this file, it will be overwritten on update + +# Longcheer makes modules that other companies rebrand, like: +# +# Alcatel One Touch X020 +# Alcatel One Touch X030 +# MobiData MBD-200HU +# ST Mobile Connect HSUPA USB Modem +# +# Most of these values were scraped from various Longcheer-based Windows +# driver .inf files. cmmdm.inf lists the actual data (ie PPP) ports, while +# cmser.inf lists the aux ports that may be either AT-capable or not but +# cannot be used for PPP. + +ACTION!="add|change|move|bind", GOTO="mm_longcheer_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1c9e", GOTO="mm_longcheer_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1bbb", GOTO="mm_tamobile_vendorcheck" +GOTO="mm_longcheer_port_types_end" + +LABEL="mm_longcheer_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="3197", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="3197", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="3197", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6000", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6000", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6000", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6060", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6060", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6060", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +# Alcatel One Touch X020 +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6061", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6061", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6061", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7001", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7001", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7001", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7001", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7002", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7101", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7101", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7101", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7101", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7102", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8000", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8000", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8000", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8000", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8001", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8001", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8001", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8001", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8002", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8002", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8002", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8002", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +# ChinaBird PL68 +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9000", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9001", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9002", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9002", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9002", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9002", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9003", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9004", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9004", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9004", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9005", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9005", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9005", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9010", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9010", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9010", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9010", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9012", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9012", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9012", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9012", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9020", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9020", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9020", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9020", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9022", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9022", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9022", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9022", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +# Zoom products +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9602", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9602", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9602", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9602", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9603", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9603", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9603", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9603", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9604", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9604", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9604", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9604", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9606", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +GOTO="mm_longcheer_port_types_end" + +LABEL="mm_tamobile_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Alcatel One Touch X060s +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +GOTO="mm_longcheer_port_types_end" + +LABEL="mm_longcheer_port_types_end" diff --git a/src/plugins/longcheer/mm-broadband-modem-longcheer.c b/src/plugins/longcheer/mm-broadband-modem-longcheer.c new file mode 100644 index 00000000..0926de2c --- /dev/null +++ b/src/plugins/longcheer/mm-broadband-modem-longcheer.c @@ -0,0 +1,416 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log.h" +#include "mm-errors-types.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-modem-helpers.h" +#include "mm-broadband-modem-longcheer.h" + +static void iface_modem_init (MMIfaceModem *iface); + +static MMIfaceModem *iface_modem_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemLongcheer, mm_broadband_modem_longcheer, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)) + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMModemModeCombination mode; + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 4); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G, 2G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + /* 2G and 3G, 3G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + const gchar *response; + const gchar *str; + gint mododr = -1; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + str = mm_strip_tag (response, "+MODODR:"); + if (!str) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse MODODR response: '%s'", + response); + return FALSE; + } + + mododr = atoi (str); + switch (mododr) { + case 1: + /* UMTS only */ + *allowed = MM_MODEM_MODE_3G; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + case 2: + /* UMTS preferred */ + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_3G; + return TRUE; + case 3: + /* GSM only */ + *allowed = MM_MODEM_MODE_2G; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + case 4: + /* GSM preferred */ + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_2G; + return TRUE; + default: + break; + } + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse unexpected MODODR response: '%s'", + response); + return FALSE; +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+MODODR?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +allowed_mode_update_ready (MMBroadbandModemLongcheer *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (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); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *command; + gint mododr = 0; + + task = g_task_new (self, NULL, callback, user_data); + + if (allowed == MM_MODEM_MODE_2G) + mododr = 3; + else if (allowed == MM_MODEM_MODE_3G) + mododr = 1; + else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { + if (preferred == MM_MODEM_MODE_2G) + mododr = 4; + else if (preferred == MM_MODEM_MODE_3G) + mododr = 2; + } else if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE) + /* Default to 3G preferred */ + mododr = 2; + + if (mododr == 0) { + 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_object_unref (task); + + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("+MODODR=%d", mododr); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)allowed_mode_update_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + const gchar *result; + + result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!result) + return FALSE; + + result = mm_strip_tag (result, "+PSRAT:"); + *access_technologies = mm_string_to_access_tech (result); + *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; + return TRUE; +} + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+PSRAT", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_unlock_retries_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + int pin1, puk1, pin2, puk2; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* That's right; no +CPNNUM: prefix, it looks like this: + * + * AT+CPNNUM + * PIN1=3; PUK1=10; PIN2=3; PUK2=10 + * OK + */ + if (sscanf (response, "PIN1=%d; PUK1=%d; PIN2=%d; PUK2=%d", &pin1, &puk1, &pin2, &puk2) == 4) { + MMUnlockRetries *retries; + retries = mm_unlock_retries_new (); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2); + g_task_return_pointer (task, retries, g_object_unref); + } else { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid unlock retries response: '%s'", + response); + } + g_object_unref (task); +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CPNNUM", + 3, + FALSE, + (GAsyncReadyCallback)load_unlock_retries_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ + +MMBroadbandModemLongcheer * +mm_broadband_modem_longcheer_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_LONGCHEER, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports AT only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_longcheer_init (MMBroadbandModemLongcheer *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; +} + +static void +mm_broadband_modem_longcheer_class_init (MMBroadbandModemLongcheerClass *klass) +{ +} diff --git a/src/plugins/longcheer/mm-broadband-modem-longcheer.h b/src/plugins/longcheer/mm-broadband-modem-longcheer.h new file mode 100644 index 00000000..710abeef --- /dev/null +++ b/src/plugins/longcheer/mm-broadband-modem-longcheer.h @@ -0,0 +1,49 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_LONGCHEER_H +#define MM_BROADBAND_MODEM_LONGCHEER_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_LONGCHEER (mm_broadband_modem_longcheer_get_type ()) +#define MM_BROADBAND_MODEM_LONGCHEER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_LONGCHEER, MMBroadbandModemLongcheer)) +#define MM_BROADBAND_MODEM_LONGCHEER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_LONGCHEER, MMBroadbandModemLongcheerClass)) +#define MM_IS_BROADBAND_MODEM_LONGCHEER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_LONGCHEER)) +#define MM_IS_BROADBAND_MODEM_LONGCHEER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_LONGCHEER)) +#define MM_BROADBAND_MODEM_LONGCHEER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_LONGCHEER, MMBroadbandModemLongcheerClass)) + +typedef struct _MMBroadbandModemLongcheer MMBroadbandModemLongcheer; +typedef struct _MMBroadbandModemLongcheerClass MMBroadbandModemLongcheerClass; + +struct _MMBroadbandModemLongcheer { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemLongcheerClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_longcheer_get_type (void); + +MMBroadbandModemLongcheer *mm_broadband_modem_longcheer_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_LONGCHEER_H */ diff --git a/src/plugins/longcheer/mm-plugin-longcheer.c b/src/plugins/longcheer/mm-plugin-longcheer.c new file mode 100644 index 00000000..31774efb --- /dev/null +++ b/src/plugins/longcheer/mm-plugin-longcheer.c @@ -0,0 +1,243 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-plugin-longcheer.h" +#include "mm-broadband-modem-longcheer.h" + +G_DEFINE_TYPE (MMPluginLongcheer, mm_plugin_longcheer, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ +/* Custom init */ + +typedef struct { + MMPortSerialAt *port; + guint retries; +} LongcheerCustomInitContext; + +static void +longcheer_custom_init_context_free (LongcheerCustomInitContext *ctx) +{ + g_object_unref (ctx->port); + g_slice_free (LongcheerCustomInitContext, ctx); +} + +static gboolean +longcheer_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void longcheer_custom_init_step (GTask *task); + +static void +gmr_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + MMPortProbe *probe; + const gchar *p; + const gchar *response; + + probe = g_task_get_source_object (task); + + response = mm_port_serial_at_command_finish (port, res, NULL); + if (!response) { + mm_obj_dbg (probe, "retrying custom init step..."); + longcheer_custom_init_step (task); + return; + } + + /* Note the lack of a ':' on the GMR; the X200 doesn't send one */ + p = mm_strip_tag (response, "AT+GMR"); + if (p && *p == 'L') { + /* X200 modems have a GMR firmware revision that starts with 'L', and + * as far as I can tell X060s devices have a revision starting with 'C'. + * So use that to determine if the device is an X200, which this plugin + * does not support since it uses a different chipset even though the + * X060s and the X200 have the exact same USB VID and PID. + */ + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "X200 cannot be supported with the Longcheer plugin"); + } else { + mm_obj_dbg (probe, "device is not a X200"); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +longcheer_custom_init_step (GTask *task) +{ + MMPortProbe *probe; + LongcheerCustomInitContext *ctx; + GCancellable *cancellable; + + probe = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + cancellable = g_task_get_cancellable (task); + + /* If cancelled, end */ + if (g_cancellable_is_cancelled (cancellable)) { + mm_obj_dbg (probe, "no need to keep on running custom init"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (ctx->retries == 0) { + /* In this case, we need the AT command result to decide whether we can + * support this modem or not, so really fail if we didn't get it. */ + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get device revision information"); + g_object_unref (task); + return; + } + + ctx->retries--; + mm_port_serial_at_command ( + ctx->port, + "AT+GMR", + 3, + FALSE, /* raw */ + FALSE, /* allow_cached */ + cancellable, + (GAsyncReadyCallback)gmr_ready, + task); +} + +static void +longcheer_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMDevice *device; + LongcheerCustomInitContext *ctx; + GTask *task; + + ctx = g_slice_new (LongcheerCustomInitContext); + ctx->port = g_object_ref (port); + ctx->retries = 3; + + task = g_task_new (probe, cancellable, callback, user_data); + /* Clears the check-cancellable flag of the task as we expect the task to + * return TRUE upon cancellation. + */ + g_task_set_check_cancellable (task, FALSE); + g_task_set_task_data (task, ctx, (GDestroyNotify)longcheer_custom_init_context_free); + + /* TCT/Alcatel in their infinite wisdom assigned the same USB VID/PID to + * the x060s (Longcheer firmware) and the x200 (something else) and thus + * we can't tell them apart via udev rules. Worse, they both report the + * same +GMM and +GMI, so we're left with just +GMR which is a sketchy way + * to tell modems apart. We can't really use Longcheer-specific commands + * like AT+MODODR or AT+PSRAT because we're not sure if they work when the + * SIM PIN has not been entered yet; many modems have a limited command + * parser before the SIM is unlocked. + */ + device = mm_port_probe_peek_device (probe); + if (mm_device_get_vendor (device) != 0x1bbb || + mm_device_get_product (device) != 0x0000) { + /* If not exactly this vendor/product, just skip */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + longcheer_custom_init_step (task); +} + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_longcheer_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + /* Vendors: Longcheer and TAMobile */ + static const guint16 vendor_ids[] = { 0x1c9e, 0x1bbb, 0 }; + /* Some TAMobile devices are different chipsets and should be handled + * by other plugins, so only handle LONGCHEER tagged devices here. + */ + static const gchar *udev_tags[] = { + "ID_MM_LONGCHEER_TAGGED", + NULL + }; + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (longcheer_custom_init), + .finish = G_CALLBACK (longcheer_custom_init_finish), + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_LONGCHEER, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + NULL)); +} + +static void +mm_plugin_longcheer_init (MMPluginLongcheer *self) +{ +} + +static void +mm_plugin_longcheer_class_init (MMPluginLongcheerClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/longcheer/mm-plugin-longcheer.h b/src/plugins/longcheer/mm-plugin-longcheer.h new file mode 100644 index 00000000..1a5f2b98 --- /dev/null +++ b/src/plugins/longcheer/mm-plugin-longcheer.h @@ -0,0 +1,42 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_LONGCHEER_H +#define MM_PLUGIN_LONGCHEER_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_LONGCHEER (mm_plugin_longcheer_get_type ()) +#define MM_PLUGIN_LONGCHEER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_LONGCHEER, MMPluginLongcheer)) +#define MM_PLUGIN_LONGCHEER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_LONGCHEER, MMPluginLongcheerClass)) +#define MM_IS_PLUGIN_LONGCHEER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_LONGCHEER)) +#define MM_IS_PLUGIN_LONGCHEER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_LONGCHEER)) +#define MM_PLUGIN_LONGCHEER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_LONGCHEER, MMPluginLongcheerClass)) + +typedef struct { + MMPlugin parent; +} MMPluginLongcheer; + +typedef struct { + MMPluginClass parent; +} MMPluginLongcheerClass; + +GType mm_plugin_longcheer_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_LONGCHEER_H */ diff --git a/src/plugins/mbm/77-mm-ericsson-mbm.rules b/src/plugins/mbm/77-mm-ericsson-mbm.rules new file mode 100644 index 00000000..73e08692 --- /dev/null +++ b/src/plugins/mbm/77-mm-ericsson-mbm.rules @@ -0,0 +1,174 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_mbm_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0bdb", GOTO="mm_mbm_ericsson_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0fce", GOTO="mm_mbm_sony_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="413c", GOTO="mm_mbm_dell_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="03f0", GOTO="mm_mbm_hp_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0930", GOTO="mm_mbm_toshiba_vendorcheck" +GOTO="mm_mbm_end" + +LABEL="mm_mbm_ericsson_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Ericsson F3507g +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1900", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1900", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1902", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1902", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F3607gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1904", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1904", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1905", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1905", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1906", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1906", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F3307 +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190a", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1909", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F3307 R2 +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1914", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C3607w +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1049", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C3607w v2 +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190b", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F5521gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190d", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190d", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1911", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1911", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson H5321gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1919", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson H5321w +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191d", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F5321gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1917", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F5321w +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191b", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C5621gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191f", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C5621w +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1921", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson H5321gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1926", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1926", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1927", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1927", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C3304w +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1928", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C5621 TFF +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1936", ENV{ID_MM_ERICSSON_MBM}="1" + +# Lenovo N5321gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="193e", ENV{ID_MM_ERICSSON_MBM}="1" + +GOTO="mm_mbm_end" + +LABEL="mm_mbm_sony_vendorcheck" + +# Sony-Ericsson MD300 +ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d0cf", ENV{ID_MM_ERICSSON_MBM}="1" + +# Sony-Ericsson MD400 +ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d0e1", ENV{ID_MM_ERICSSON_MBM}="1" + +# Sony-Ericsson MD400G +ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d103", ENV{ID_MM_ERICSSON_MBM}="1" + +GOTO="mm_mbm_end" + +LABEL="mm_mbm_dell_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Dell 5560 +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818e", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818e", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" + +# Dell 5550 +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818d", ENV{ID_MM_ERICSSON_MBM}="1" + +# Dell 5530 HSDPA +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{ID_MM_ERICSSON_MBM}="1" + +# Dell F3607gw +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8183", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8183", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8184", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8184", ENV{ID_MM_ERICSSON_MBM}="1" + +# Dell F3307 +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818b", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818b", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818c", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818c", ENV{ID_MM_ERICSSON_MBM}="1" + +GOTO="mm_mbm_end" + +LABEL="mm_mbm_hp_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# HP hs2330 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="271d", ENV{ID_MM_ERICSSON_MBM}="1" + +# HP hs2320 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="261d", ENV{ID_MM_ERICSSON_MBM}="1" + +# HP hs2340 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3a1d", ENV{ID_MM_ERICSSON_MBM}="1" + +# HP hs2350 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3d1d", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3d1d", ENV{ID_MM_ERICSSON_MBM}="1" + +# HP lc2000 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="301d", ENV{ID_MM_ERICSSON_MBM}="1" + +# HP lc2010 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="2f1d", ENV{ID_MM_ERICSSON_MBM}="1" + +GOTO="mm_mbm_end" + +LABEL="mm_mbm_toshiba_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Toshiba +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130b", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130b", ENV{ID_MM_ERICSSON_MBM}="1" + +# Toshiba F3607gw +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130c", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130c", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1311", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1311", ENV{ID_MM_ERICSSON_MBM}="1" + +# Toshiba F3307 +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1315", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1316", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1317", ENV{ID_MM_ERICSSON_MBM}="1" + +# Toshiba F5521gw +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1313", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1314", ENV{ID_MM_ERICSSON_MBM}="1" + +# Toshiba H5321gw +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1319", ENV{ID_MM_ERICSSON_MBM}="1" + +GOTO="mm_mbm_end" + +LABEL="mm_mbm_end" diff --git a/src/plugins/mbm/mm-broadband-bearer-mbm.c b/src/plugins/mbm/mm-broadband-bearer-mbm.c new file mode 100644 index 00000000..c1407ab4 --- /dev/null +++ b/src/plugins/mbm/mm-broadband-bearer-mbm.c @@ -0,0 +1,911 @@ +/* -*- 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) 2008 - 2010 Ericsson AB + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * Copyright (C) 2017 Aleksander Morgado <aleksander@aleksander.es> + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + * Bjorn Runaker <bjorn.runaker@ericsson.com> + * Torgny Johansson <torgny.johansson@ericsson.com> + * Jonas Sjöquist <jonas.sjoquist@ericsson.com> + * Dan Williams <dcbw@redhat.com> + * Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-mbm.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" +#include "mm-daemon-enums-types.h" + +G_DEFINE_TYPE (MMBroadbandBearerMbm, mm_broadband_bearer_mbm, MM_TYPE_BROADBAND_BEARER) + +struct _MMBroadbandBearerMbmPrivate { + GTask *connect_pending; + GTask *disconnect_pending; +}; + +/*****************************************************************************/ +/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + guint cid; + MMPort *data; + guint poll_count; + guint poll_id; + GError *saved_error; +} Dial3gppContext; + +static void +dial_3gpp_context_free (Dial3gppContext *ctx) +{ + g_assert (!ctx->poll_id); + g_assert (!ctx->saved_error); + g_clear_object (&ctx->data); + g_clear_object (&ctx->primary); + g_clear_object (&ctx->modem); + g_slice_free (Dial3gppContext, ctx); +} + +static MMPort * +dial_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return MM_PORT (g_task_propagate_pointer (G_TASK (res), error)); +} + +static void +connect_reset_ready (MMBroadbandBearer *self, + GAsyncResult *res, + GTask *task) +{ + Dial3gppContext *ctx; + + ctx = g_task_get_task_data (task); + + MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp_finish (self, res, NULL); + + /* When reset is requested, it was either cancelled or an error was stored */ + if (!g_task_return_error_if_cancelled (task)) { + g_assert (ctx->saved_error); + g_task_return_error (task, ctx->saved_error); + ctx->saved_error = NULL; + } + + g_object_unref (task); +} + +static void +connect_reset (GTask *task) +{ + MMBroadbandBearerMbm *self; + Dial3gppContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp ( + MM_BROADBAND_BEARER (self), + MM_BROADBAND_MODEM (ctx->modem), + ctx->primary, + NULL, + ctx->data, + ctx->cid, + (GAsyncReadyCallback) connect_reset_ready, + task); +} + +static void +process_pending_connect_attempt (MMBroadbandBearerMbm *self, + MMBearerConnectionStatus status) +{ + GTask *task; + Dial3gppContext *ctx; + + /* Recover connection task */ + task = self->priv->connect_pending; + self->priv->connect_pending = NULL; + g_assert (task != NULL); + + ctx = g_task_get_task_data (task); + + if (ctx->poll_id) { + g_source_remove (ctx->poll_id); + ctx->poll_id = 0; + } + + /* Received 'CONNECTED' during a connection attempt? */ + if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED) { + /* If we wanted to get cancelled before, do it now. */ + if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) { + connect_reset (task); + return; + } + + g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); + g_object_unref (task); + return; + } + + /* If we wanted to get cancelled before and now we couldn't connect, + * use the cancelled error and return */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + /* Otherwise, received 'DISCONNECTED' during a connection attempt? */ + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Call setup failed"); + g_object_unref (task); +} + +static gboolean connect_poll_cb (MMBroadbandBearerMbm *self); + +static void +connect_poll_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerMbm *self) +{ + GTask *task; + Dial3gppContext *ctx; + GError *error = NULL; + const gchar *response; + guint state; + + task = g_steal_pointer (&self->priv->connect_pending); + + if (!task) { + mm_obj_dbg (self, "connection context was finished already by an unsolicited message"); + /* Run _finish() to finalize the async call, even if we don't care + * the result */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + return; + } + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (!response) { + ctx->saved_error = error; + connect_reset (task); + return; + } + + if (sscanf (response, "*ENAP: %d", &state) == 1 && state == 1) { + /* Success! Connected... */ + g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); + g_object_unref (task); + return; + } + + /* Restore pending task and check again in one second */ + self->priv->connect_pending = task; + g_assert (ctx->poll_id == 0); + ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc) connect_poll_cb, self); +} + +static gboolean +connect_poll_cb (MMBroadbandBearerMbm *self) +{ + GTask *task; + Dial3gppContext *ctx; + + task = g_steal_pointer (&self->priv->connect_pending); + + g_assert (task); + ctx = g_task_get_task_data (task); + + ctx->poll_id = 0; + + /* Complete if we were cancelled */ + if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) { + connect_reset (task); + return G_SOURCE_REMOVE; + } + + /* Too many retries... */ + if (ctx->poll_count > MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT) { + g_assert (!ctx->saved_error); + ctx->saved_error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, + "Connection attempt timed out"); + connect_reset (task); + return G_SOURCE_REMOVE; + } + + /* Restore pending task and poll */ + self->priv->connect_pending = task; + ctx->poll_count++; + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "AT*ENAP?", + 3, + FALSE, + FALSE, /* raw */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)connect_poll_ready, + self); + return G_SOURCE_REMOVE; +} + +static void +activate_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerMbm *self) +{ + GTask *task; + Dial3gppContext *ctx; + GError *error = NULL; + + /* Try to recover the connection context. If none found, it means the + * context was already completed and we have nothing else to do. */ + task = g_steal_pointer (&self->priv->connect_pending); + + if (!task) { + mm_obj_dbg (self, "connection context was finished already by an unsolicited message"); + /* Run _finish() to finalize the async call, even if we don't care + * the result */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + goto out; + } + + /* From now on, if we get cancelled, we'll need to run the connection + * reset ourselves just in case */ + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + goto out; + } + + ctx = g_task_get_task_data (task); + + /* No unsolicited E2NAP status yet; wait for it and periodically poll + * to handle very old F3507g/MD300 firmware that may not send E2NAP. */ + self->priv->connect_pending = task; + ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc)connect_poll_cb, self); + + out: + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); +} + +static void +activate (GTask *task) +{ + MMBroadbandBearerMbm *self; + Dial3gppContext *ctx; + gchar *command; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* The unsolicited response to ENAP may come before the OK does. + * We will keep the connection context in the bearer private data so + * that it is accessible from the unsolicited message handler. */ + g_assert (self->priv->connect_pending == NULL); + self->priv->connect_pending = task; + + /* Activate the PDP context and start the data session */ + command = g_strdup_printf ("AT*ENAP=1,%d", ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 10, + FALSE, + FALSE, /* raw */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)activate_ready, + g_object_ref (self)); /* we pass the bearer object! */ + g_free (command); +} + +static void +authenticate_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + activate (task); +} + +static void +authenticate (GTask *task) +{ + MMBroadbandBearerMbm *self; + Dial3gppContext *ctx; + const gchar *user; + const gchar *password; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + + /* Both user and password are required; otherwise firmware returns an error */ + if (user || password) { + g_autofree gchar *command = NULL; + g_autofree gchar *user_enc = NULL; + g_autofree gchar *password_enc = NULL; + GError *error = NULL; + + user_enc = mm_modem_charset_str_from_utf8 (user, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (ctx->modem)), + FALSE, + &error); + if (!user_enc) { + g_prefix_error (&error, "Couldn't convert user to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + password_enc = mm_modem_charset_str_from_utf8 (password, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (ctx->modem)), + FALSE, + &error); + if (!password_enc) { + g_prefix_error (&error, "Couldn't convert password to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + command = g_strdup_printf ("AT*EIAAUW=%d,1,\"%s\",\"%s\"", + ctx->cid, user_enc, password_enc); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, /* raw */ + g_task_get_cancellable (task), + (GAsyncReadyCallback) authenticate_ready, + task); + return; + } + + mm_obj_dbg (self, "authentication not needed"); + activate (task); +} + +static void +dial_3gpp (MMBroadbandBearer *_self, + MMBaseModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self); + GTask *task; + Dial3gppContext *ctx; + + g_assert (primary != NULL); + + task = g_task_new (self, cancellable, callback, user_data); + + ctx = g_slice_new0 (Dial3gppContext); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + g_task_set_task_data (task, ctx, (GDestroyNotify)dial_3gpp_context_free); + + /* We need a net data port */ + ctx->data = mm_base_modem_get_best_data_port (modem, MM_PORT_TYPE_NET); + if (!ctx->data) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to launch connection"); + g_object_unref (task); + return; + } + + authenticate (task); +} + +/*****************************************************************************/ +/* 3GPP IP config retrieval (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + MMBearerIpFamily family; +} GetIpConfig3gppContext; + +static void +get_ip_config_context_free (GetIpConfig3gppContext *ctx) +{ + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_free (ctx); +} + +static gboolean +get_ip_config_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + MMBearerIpConfig **ipv4_config, + MMBearerIpConfig **ipv6_config, + GError **error) +{ + MMBearerConnectResult *configs; + MMBearerIpConfig *ipv4, *ipv6; + + configs = g_task_propagate_pointer (G_TASK (res), error); + if (!configs) + return FALSE; + + ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs); + ipv6 = mm_bearer_connect_result_peek_ipv6_config (configs); + g_assert (ipv4 || ipv6); + if (ipv4_config && ipv4) + *ipv4_config = g_object_ref (ipv4); + if (ipv6_config && ipv6) + *ipv6_config = g_object_ref (ipv6); + + mm_bearer_connect_result_unref (configs); + return TRUE; +} + +static void +ip_config_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GetIpConfig3gppContext *ctx; + MMBearerIpConfig *ipv4_config = NULL; + MMBearerIpConfig *ipv6_config = NULL; + const gchar *response; + GError *error = NULL; + MMBearerConnectResult *connect_result; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + g_error_free (error); + + /* Fall back to DHCP configuration; early devices don't support *E2IPCFG */ + if (ctx->family == MM_BEARER_IP_FAMILY_IPV4 || ctx->family == MM_BEARER_IP_FAMILY_IPV4V6) { + ipv4_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP); + } + if (ctx->family == MM_BEARER_IP_FAMILY_IPV6 || ctx->family == MM_BEARER_IP_FAMILY_IPV4V6) { + ipv6_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); + } + } else { + if (!mm_mbm_parse_e2ipcfg_response (response, + &ipv4_config, + &ipv6_config, + &error)) { + g_task_return_error (task, error); + goto out; + } + + if (!ipv4_config && !ipv6_config) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get IP config: couldn't parse response '%s'", + response); + goto out; + } + } + + connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->primary), + ipv4_config, + ipv6_config); + g_task_return_pointer (task, + connect_result, + (GDestroyNotify)mm_bearer_connect_result_unref); + +out: + g_object_unref (task); + g_clear_object (&ipv4_config); + g_clear_object (&ipv6_config); +} + +static void +get_ip_config_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + MMBearerIpFamily ip_family, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GetIpConfig3gppContext *ctx; + GTask *task; + + ctx = g_new0 (GetIpConfig3gppContext, 1); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + ctx->family = ip_family; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)get_ip_config_context_free); + + mm_base_modem_at_command_full (MM_BASE_MODEM (modem), + primary, + "*E2IPCFG?", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)ip_config_ready, + task); +} + +/*****************************************************************************/ +/* 3GPP disconnect */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + guint poll_count; + guint poll_id; +} DisconnectContext; + +static void +disconnect_context_free (DisconnectContext *ctx) +{ + g_assert (!ctx->poll_id); + g_clear_object (&ctx->primary); + g_clear_object (&ctx->modem); + g_free (ctx); +} + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +process_pending_disconnect_attempt (MMBroadbandBearerMbm *self, + MMBearerConnectionStatus status) +{ + GTask *task; + DisconnectContext *ctx; + + /* Recover disconnection task */ + task = g_steal_pointer (&self->priv->disconnect_pending); + ctx = g_task_get_task_data (task); + + if (ctx->poll_id) { + g_source_remove (ctx->poll_id); + ctx->poll_id = 0; + } + + /* Received 'DISCONNECTED' during a disconnection attempt? */ + if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) { + mm_obj_dbg (self, "connection disconnect indicated by an unsolicited message"); + g_task_return_boolean (task, TRUE); + } else { + /* Otherwise, report error */ + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Disconnection failed"); + } + g_object_unref (task); +} + +static gboolean disconnect_poll_cb (MMBroadbandBearerMbm *self); + +static void +disconnect_poll_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerMbm *self) + +{ + GTask *task; + DisconnectContext *ctx; + GError *error = NULL; + const gchar *response; + guint state; + + task = g_steal_pointer (&self->priv->disconnect_pending); + + if (!task) { + mm_obj_dbg (self, "disconnection context was finished already by an unsolicited message"); + /* Run _finish() to finalize the async call, even if we don't care + * the result */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + goto out; + } + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + goto out; + } + + if (sscanf (response, "*ENAP: %d", &state) == 1 && state == 0) { + /* Disconnected */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + goto out; + } + + /* Restore pending task and check in 1s */ + self->priv->disconnect_pending = task; + ctx = g_task_get_task_data (task); + g_assert (ctx->poll_id == 0); + ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc) disconnect_poll_cb, self); + + out: + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); +} + +static gboolean +disconnect_poll_cb (MMBroadbandBearerMbm *self) +{ + GTask *task; + DisconnectContext *ctx; + + task = self->priv->disconnect_pending; + self->priv->disconnect_pending = NULL; + + g_assert (task); + ctx = g_task_get_task_data (task); + + ctx->poll_id = 0; + + /* Too many retries... */ + if (ctx->poll_count > MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT) { + g_task_return_new_error (task, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, + "Disconnection attempt timed out"); + g_object_unref (task); + return G_SOURCE_REMOVE; + } + + /* Restore pending task and poll */ + self->priv->disconnect_pending = task; + ctx->poll_count++; + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "AT*ENAP?", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback) disconnect_poll_ready, + g_object_ref (self)); /* we pass the bearer object! */ + return G_SOURCE_REMOVE; +} + +static void +disconnect_enap_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerMbm *self) +{ + DisconnectContext *ctx; + GTask *task; + GError *error = NULL; + + task = g_steal_pointer (&self->priv->disconnect_pending); + + /* Try to recover the disconnection context. If none found, it means the + * context was already completed and we have nothing else to do. */ + if (!task) { + mm_base_modem_at_command_full_finish (modem, res, NULL); + goto out; + } + + ctx = g_task_get_task_data (task); + + /* Ignore errors for now */ + mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + mm_obj_dbg (self, "disconnection failed (not fatal): %s", error->message); + g_error_free (error); + } + + /* No unsolicited E2NAP status yet; wait for it and periodically poll + * to handle very old F3507g/MD300 firmware that may not send E2NAP. */ + self->priv->disconnect_pending = task; + ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc)disconnect_poll_cb, self); + + out: + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); +} + +static void +disconnect_3gpp (MMBroadbandBearer *_self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self); + GTask *task; + DisconnectContext *ctx; + + g_assert (primary != NULL); + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_new0 (DisconnectContext, 1); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + g_task_set_task_data (task, ctx, (GDestroyNotify) disconnect_context_free); + + /* The unsolicited response to ENAP may come before the OK does. + * We will keep the disconnection context in the bearer private data so + * that it is accessible from the unsolicited message handler. */ + g_assert (self->priv->disconnect_pending == NULL); + self->priv->disconnect_pending = task; + + mm_base_modem_at_command_full (MM_BASE_MODEM (modem), + primary, + "*ENAP=0", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)disconnect_enap_ready, + g_object_ref (self)); /* we pass the bearer object! */ +} + +/*****************************************************************************/ + +static void +report_connection_status (MMBaseBearer *_self, + MMBearerConnectionStatus status, + const GError *connection_error) +{ + MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self); + + g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED || + status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED || + status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED); + + /* Process pending connection attempt */ + if (self->priv->connect_pending) { + process_pending_connect_attempt (self, status); + return; + } + + /* Process pending disconnection attempt */ + if (self->priv->disconnect_pending) { + process_pending_disconnect_attempt (self, status); + return; + } + + mm_obj_dbg (self, "received spontaneous E2NAP (%s)", + mm_bearer_connection_status_get_string (status)); + + /* Received a random 'DISCONNECTED'...*/ + if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED || + status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED) { + /* If no connection/disconnection attempt on-going, make sure we mark ourselves as + * disconnected. Make sure we only pass 'DISCONNECTED' to the parent */ + MM_BASE_BEARER_CLASS (mm_broadband_bearer_mbm_parent_class)->report_connection_status ( + _self, + MM_BEARER_CONNECTION_STATUS_DISCONNECTED, + NULL); + } +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_broadband_bearer_mbm_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *bearer; + GObject *source; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_mbm_new (MMBroadbandModemMbm *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_MBM, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + NULL); +} + +static void +mm_broadband_bearer_mbm_init (MMBroadbandBearerMbm *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_BEARER_MBM, + MMBroadbandBearerMbmPrivate); +} + +static void +mm_broadband_bearer_mbm_class_init (MMBroadbandBearerMbmClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandBearerMbmPrivate)); + + base_bearer_class->report_connection_status = report_connection_status; + base_bearer_class->load_connection_status = NULL; + base_bearer_class->load_connection_status_finish = NULL; +#if defined WITH_SUSPEND_RESUME + base_bearer_class->reload_connection_status = NULL; + base_bearer_class->reload_connection_status_finish = NULL; +#endif + + broadband_bearer_class->dial_3gpp = dial_3gpp; + broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; + broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp; + broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; +} diff --git a/src/plugins/mbm/mm-broadband-bearer-mbm.h b/src/plugins/mbm/mm-broadband-bearer-mbm.h new file mode 100644 index 00000000..a05b456c --- /dev/null +++ b/src/plugins/mbm/mm-broadband-bearer-mbm.h @@ -0,0 +1,68 @@ +/* -*- 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) 2008 - 2010 Ericsson AB + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + * Bjorn Runaker <bjorn.runaker@ericsson.com> + * Torgny Johansson <torgny.johansson@ericsson.com> + * Jonas Sjöquist <jonas.sjoquist@ericsson.com> + * Dan Williams <dcbw@redhat.com> + * Aleksander Morgado <aleksander@lanedo.com> + */ + +#ifndef MM_BROADBAND_BEARER_MBM_H +#define MM_BROADBAND_BEARER_MBM_H + +#include <glib.h> +#include <glib-object.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-mbm.h" + +#define MM_TYPE_BROADBAND_BEARER_MBM (mm_broadband_bearer_mbm_get_type ()) +#define MM_BROADBAND_BEARER_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbm)) +#define MM_BROADBAND_BEARER_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbmClass)) +#define MM_IS_BROADBAND_BEARER_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_MBM)) +#define MM_IS_BROADBAND_BEARER_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_MBM)) +#define MM_BROADBAND_BEARER_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbmClass)) + +typedef struct _MMBroadbandBearerMbm MMBroadbandBearerMbm; +typedef struct _MMBroadbandBearerMbmClass MMBroadbandBearerMbmClass; +typedef struct _MMBroadbandBearerMbmPrivate MMBroadbandBearerMbmPrivate; + +struct _MMBroadbandBearerMbm { + MMBroadbandBearer parent; + MMBroadbandBearerMbmPrivate *priv; +}; + +struct _MMBroadbandBearerMbmClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_mbm_get_type (void); + +/* Default 3GPP bearer creation implementation */ +void mm_broadband_bearer_mbm_new (MMBroadbandModemMbm *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_mbm_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_BROADBAND_BEARER_MBM_H */ diff --git a/src/plugins/mbm/mm-broadband-modem-mbm.c b/src/plugins/mbm/mm-broadband-modem-mbm.c new file mode 100644 index 00000000..fbc9830c --- /dev/null +++ b/src/plugins/mbm/mm-broadband-modem-mbm.c @@ -0,0 +1,1583 @@ +/* -*- 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) 2008 - 2010 Ericsson AB + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + * Bjorn Runaker <bjorn.runaker@ericsson.com> + * Torgny Johansson <torgny.johansson@ericsson.com> + * Jonas Sjöquist <jonas.sjoquist@ericsson.com> + * Dan Williams <dcbw@redhat.com> + * Aleksander Morgado <aleksander@lanedo.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-bearer-list.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" +#include "mm-broadband-modem-mbm.h" +#include "mm-broadband-bearer-mbm.h" +#include "mm-sim-mbm.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-location.h" + +/* sets the interval in seconds on how often the card emits the NMEA sentences */ +#define MBM_GPS_NMEA_INTERVAL "5" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbm, mm_broadband_modem_mbm, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)) + +#define MBM_E2NAP_DISCONNECTED 0 +#define MBM_E2NAP_CONNECTED 1 +#define MBM_E2NAP_CONNECTING 2 + +struct _MMBroadbandModemMbmPrivate { + gboolean have_emrdy; + + GRegex *e2nap_regex; + GRegex *e2nap_ext_regex; + GRegex *emrdy_regex; + GRegex *pacsp_regex; + GRegex *estksmenu_regex; + GRegex *estksms_regex; + GRegex *emwi_regex; + GRegex *erinfo_regex; + + MMModemLocationSource enabled_sources; + + guint mbm_mode; +}; + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_mbm_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_mbm_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + + g_object_unref (task); +} + +static void +modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_obj_dbg (self, "creating MBM bearer..."); + mm_broadband_bearer_mbm_new (MM_BROADBAND_MODEM_MBM (self), + properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_mbm_new_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Create SIM (Modem interface) */ + +static MMBaseSim * +create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_mbm_new_finish (res, error); +} + +static void +create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* New MBM SIM */ + mm_sim_mbm_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + +/*****************************************************************************/ +/* After SIM unlock (Modem interface) */ + +static gboolean +modem_after_sim_unlock_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +after_sim_unlock_wait_cb (GTask *task) +{ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +modem_after_sim_unlock (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* wait so sim pin is done */ + g_timeout_add (500, (GSourceFunc)after_sim_unlock_wait_cb, task); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *_self, + GAsyncResult *res, + GError **error) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + const gchar *response; + guint32 mask = 0; + GArray *combinations; + MMModemModeCombination mode; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + if (!mm_mbm_parse_cfun_test (response, self, &mask, error)) + return FALSE; + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3); + + /* 2G only */ + if (mask & (1 << MBM_NETWORK_MODE_2G)) { + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + + /* 3G only */ + if (mask & (1 << MBM_NETWORK_MODE_3G)) { + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + + /* 2G and 3G */ + if (mask & (1 << MBM_NETWORK_MODE_ANY)) { + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + + if (combinations->len == 0) { + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't load any supported mode"); + g_array_unref (combinations); + return NULL; + } + + return combinations; +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +static gboolean +load_current_modes_finish (MMIfaceModem *_self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + const gchar *response; + gint mbm_mode = -1; + + g_assert (allowed); + g_assert (preferred); + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response || !mm_mbm_parse_cfun_query_current_modes (response, allowed, &mbm_mode, error)) + return FALSE; + + /* No settings to set preferred */ + *preferred = MM_MODEM_MODE_NONE; + + if (mbm_mode != -1) + self->priv->mbm_mode = mbm_mode; + + return TRUE; +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +typedef struct { + gint mbm_mode; +} SetCurrentModesContext; + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +allowed_mode_update_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + SetCurrentModesContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_finish (self, res, &error); + if (error) + /* Let the error be critical. */ + g_task_return_error (task, error); + else { + /* Cache current allowed mode */ + MM_BROADBAND_MODEM_MBM (self)->priv->mbm_mode = ctx->mbm_mode; + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SetCurrentModesContext *ctx; + GTask *task; + gchar *command; + + ctx = g_new (SetCurrentModesContext, 1); + ctx->mbm_mode = -1; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + if (allowed == MM_MODEM_MODE_2G) + ctx->mbm_mode = MBM_NETWORK_MODE_2G; + else if (allowed == MM_MODEM_MODE_3G) + ctx->mbm_mode = MBM_NETWORK_MODE_3G; + else if ((allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G) || + allowed == MM_MODEM_MODE_ANY) && + preferred == MM_MODEM_MODE_NONE) + ctx->mbm_mode = MBM_NETWORK_MODE_ANY; + + if (ctx->mbm_mode < 0) { + 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_object_unref (task); + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("+CFUN=%d", ctx->mbm_mode); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)allowed_mode_update_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Initializing the modem (during first enabling) */ + +static gboolean +enabling_modem_init_finish (MMBroadbandModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +enabling_init_sequence_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + /* Ignore errors */ + mm_base_modem_at_sequence_full_finish (self, res, NULL, NULL); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static const MMBaseModemAtCommand enabling_modem_init_sequence[] = { + /* Init command */ + { "&F", 3, FALSE, NULL }, + /* Ensure disconnected */ + { "*ENAP=0", 3, FALSE, NULL }, + { NULL } +}; + +static void +run_enabling_init_sequence (GTask *task) +{ + MMBaseModem *self; + + self = g_task_get_source_object (task); + mm_base_modem_at_sequence_full (self, + mm_base_modem_peek_port_primary (self), + enabling_modem_init_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)enabling_init_sequence_ready, + task); +} + +static void +emrdy_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + /* EMRDY unsolicited response might have happened between the command + * submission and the response. This was seen once: + * + * (ttyACM0): --> 'AT*EMRDY?<CR>' + * (ttyACM0): <-- 'T*EMRD<CR><LF>*EMRDY: 1<CR><LF>Y?' + * + * So suppress the warning if the unsolicited handler handled the response + * before we get here. + */ + if (!mm_base_modem_at_command_finish (self, res, &error)) { + if (g_error_matches (error, + MM_SERIAL_ERROR, + MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) + mm_obj_warn (self, "timed out waiting for EMRDY response"); + else + MM_BROADBAND_MODEM_MBM (self)->priv->have_emrdy = TRUE; + g_error_free (error); + } + + run_enabling_init_sequence (task); +} + +static void +enabling_modem_init (MMBroadbandModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Modem is ready?, no need to check EMRDY */ + if (self->priv->have_emrdy) { + run_enabling_init_sequence (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*EMRDY?", + 3, + FALSE, + (GAsyncReadyCallback)emrdy_ready, + task); +} + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Use AT+CFUN=4 for power down. It will stop the RF (IMSI detach), and + * keeps access to the SIM */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=4", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Powering up the modem (Modem interface) */ + +static gboolean +modem_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + /* By default, errors in the power up command are ignored. */ + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL); + return TRUE; +} + +static void +modem_power_up (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + gchar *command; + + g_assert (self->priv->mbm_mode == MBM_NETWORK_MODE_ANY || + self->priv->mbm_mode == MBM_NETWORK_MODE_2G || + self->priv->mbm_mode == MBM_NETWORK_MODE_3G); + + command = g_strdup_printf ("+CFUN=%u", self->priv->mbm_mode); + mm_base_modem_at_command (MM_BASE_MODEM (self), + command, + 5, + FALSE, + callback, + user_data); + g_free (command); +} + +/*****************************************************************************/ +/* Power state loading (Modem interface) */ + +static MMModemPowerState +load_power_state_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + const gchar *response; + MMModemPowerState state; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response || !mm_mbm_parse_cfun_query_power_state (response, &state, error)) + return MM_MODEM_POWER_STATE_UNKNOWN; + + return state; +} + +static void +load_power_state (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Reset (Modem interface) */ + +static gboolean +reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + /* Ignore errors */ + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL); + return TRUE; +} + +static void +reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*E2RESET", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Factory reset (Modem interface) */ + +static gboolean +factory_reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + /* Ignore errors */ + mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, NULL); + return TRUE; +} + +static const MMBaseModemAtCommand factory_reset_sequence[] = { + /* Init command */ + { "&F +CMEE=0", 3, FALSE, NULL }, + { "+COPS=0", 3, FALSE, NULL }, + { "+CR=0", 3, FALSE, NULL }, + { "+CRC=0", 3, FALSE, NULL }, + { "+CREG=0", 3, FALSE, NULL }, + { "+CMER=0", 3, FALSE, NULL }, + { "*EPEE=0", 3, FALSE, NULL }, + { "+CNMI=2, 0, 0, 0, 0", 3, FALSE, NULL }, + { "+CGREG=0", 3, FALSE, NULL }, + { "*EIAD=0", 3, FALSE, NULL }, + { "+CGSMS=3", 3, FALSE, NULL }, + { "+CSCA=\"\",129", 3, FALSE, NULL }, + { NULL } +}; + +static void +factory_reset (MMIfaceModem *self, + const gchar *code, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_obj_dbg (self, "ignoring user-provided factory reset code: '%s'", code); + + mm_base_modem_at_sequence (MM_BASE_MODEM (self), + factory_reset_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + callback, + user_data); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + MMUnlockRetries *unlock_retries; + const gchar *response; + gint matched; + guint a, b, c ,d; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return NULL; + + matched = sscanf (response, "*EPIN: %d, %d, %d, %d", + &a, &b, &c, &d); + if (matched != 4) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Could not parse PIN retries results: '%s'", + response); + return NULL; + } + + if (a > 998) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid PIN attempts left: '%u'", + a); + return NULL; + } + + unlock_retries = mm_unlock_retries_new (); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN, a); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK, b); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN2, c); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK2, d); + return unlock_retries; +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*EPIN?", + 10, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +typedef struct { + MMBearerConnectionStatus status; +} BearerListReportStatusForeachContext; + +static void +bearer_list_report_status_foreach (MMBaseBearer *bearer, + BearerListReportStatusForeachContext *ctx) +{ + mm_base_bearer_report_connection_status (bearer, ctx->status); +} + +static void +e2nap_received (MMPortSerialAt *port, + GMatchInfo *info, + MMBroadbandModemMbm *self) +{ + MMBearerList *list = NULL; + guint state; + BearerListReportStatusForeachContext ctx; + + if (!mm_get_uint_from_match_info (info, 1, &state)) + return; + + ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN; + + switch (state) { + case MBM_E2NAP_DISCONNECTED: + mm_obj_dbg (self, "disconnected"); + ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED; + break; + case MBM_E2NAP_CONNECTED: + mm_obj_dbg (self, "connected"); + ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED; + break; + case MBM_E2NAP_CONNECTING: + mm_obj_dbg (self, "connecting"); + break; + default: + /* Should not happen */ + mm_obj_dbg (self, "unhandled E2NAP state %d", state); + } + + /* If unknown status, don't try to report anything */ + if (ctx.status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) + return; + + /* If empty bearer list, nothing else to do */ + g_object_get (self, + MM_IFACE_MODEM_BEARER_LIST, &list, + NULL); + if (!list) + return; + + mm_bearer_list_foreach (list, + (MMBearerListForeachFunc)bearer_list_report_status_foreach, + &ctx); + g_object_unref (list); +} + +static void +erinfo_received (MMPortSerialAt *port, + GMatchInfo *info, + MMBroadbandModemMbm *self) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + guint mode; + + if (mm_get_uint_from_match_info (info, 2, &mode)) { + switch (mode) { + case 1: + act = MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + break; + case 2: + act = MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + break; + default: + break; + } + } + + /* 3G modes take precedence */ + if (mm_get_uint_from_match_info (info, 3, &mode)) { + switch (mode) { + case 1: + act = MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + break; + case 2: + act = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; + break; + case 3: + act = MM_MODEM_ACCESS_TECHNOLOGY_HSPA; + break; + default: + break; + } + } + + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + act, + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); +} + +static void +set_unsolicited_events_handlers (MMBroadbandModemMbm *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* Access technology related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->erinfo_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)erinfo_received : NULL, + enable ? self : NULL, + NULL); + + /* Connection related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->e2nap_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)e2nap_received : NULL, + enable ? self : NULL, + NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->e2nap_ext_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)e2nap_received : NULL, + enable ? self : NULL, + NULL); + } +} + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static void +parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own cleanup first */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Enabling unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +own_enable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static const MMBaseModemAtCommand unsolicited_enable_sequence[] = { + { "*ERINFO=1", 5, FALSE, NULL }, + { "*E2NAP=1", 5, FALSE, NULL }, + { NULL } +}; + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Our own enable now */ + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_enable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_enable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's enable */ + iface_modem_3gpp_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Disabling unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +own_disable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Next, chain up parent's disable */ + iface_modem_3gpp_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, + task); +} + +static const MMBaseModemAtCommand unsolicited_disable_sequence[] = { + { "*ERINFO=0", 5, FALSE, NULL }, + { "*E2NAP=0", 5, FALSE, NULL }, + { NULL } +}; + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own disable first */ + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_disable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_disable_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +static MMModemLocationSource +location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + + sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* not sure how to check if GPS is supported, just allow it */ + if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) + sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED); + + /* So we're done, complete */ + g_task_return_int (task, sources); + g_object_unref (task); +} + +static void +location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_location_parent->load_capabilities ( + self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Enable/Disable location gathering (Location interface) */ + +typedef struct { + MMModemLocationSource source; +} LocationGatheringContext; + +/******************************/ +/* Disable location gathering */ + +static gboolean +disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LocationGatheringContext *ctx; + MMPortSerialGps *gps_port; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_full_finish (self, res, &error); + + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + /* Even if we get an error here, we try to close the GPS port */ + gps_port = mm_base_modem_peek_port_gps (self); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +disable_location_gathering (MMIfaceModemLocation *_self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + gboolean stop_gps = FALSE; + LocationGatheringContext *ctx; + GTask *task; + + ctx = g_new (LocationGatheringContext, 1); + ctx->source = source; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + /* Only stop GPS engine if no GPS-related sources enabled */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + self->priv->enabled_sources &= ~source; + + if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + stop_gps = TRUE; + } + + if (stop_gps) { + mm_base_modem_at_command_full (MM_BASE_MODEM (_self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (_self)), + "AT*E2GPSCTL=0", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_disabled_ready, + task); + return; + } + + /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +static gboolean +enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_enabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LocationGatheringContext *ctx; + GError *error = NULL; + MMPortSerialGps *gps_port; + + if (!mm_base_modem_at_command_full_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + gps_port = mm_base_modem_peek_port_gps (self); + if (!gps_port || + !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't open raw GPS serial port"); + } else { + GByteArray *buf; + const gchar *command = "ATE0*E2GPSNPD\r\n"; + + /* We need to send an AT command to the GPS data port to + * toggle it into this data mode. This is a particularity of + * mbm cards where the GPS data port is not hard wired. So + * we need to use the MMPortSerial API here. + */ + buf = g_byte_array_new (); + g_byte_array_append (buf, (const guint8 *) command, strlen (command)); + mm_port_serial_command (MM_PORT_SERIAL (gps_port), + buf, + 3, + FALSE, /* never cached */ + FALSE, /* always queued last */ + NULL, + NULL, + NULL); + g_byte_array_unref (buf); + g_task_return_boolean (task, TRUE); + } + + } else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + LocationGatheringContext *ctx; + gboolean start_gps = FALSE; + GError *error = NULL; + + if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Now our own enabling */ + + /* NMEA and RAW are both enabled in the same way */ + ctx = g_task_get_task_data (task); + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + /* Only start GPS engine if not done already */ + if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + start_gps = TRUE; + self->priv->enabled_sources |= ctx->source; + } + + if (start_gps) { + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + "AT*E2GPSCTL=1," MBM_GPS_NMEA_INTERVAL ",0", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_enabled_ready, + task); + return; + } + + /* For any other location (e.g. 3GPP), or if GPS already running just return */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LocationGatheringContext *ctx; + GTask *task; + + ctx = g_new (LocationGatheringContext, 1); + ctx->source = source; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + /* Chain up parent's gathering enable */ + iface_modem_location_parent->enable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +emrdy_received (MMPortSerialAt *port, + GMatchInfo *info, + MMBroadbandModemMbm *self) +{ + self->priv->have_emrdy = TRUE; +} + +static void +gps_trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +static void +setup_ports (MMBroadbandModem *_self) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + MMPortSerialAt *ports[2]; + MMPortSerialGps *gps_data_port; + guint i; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbm_parent_class)->setup_ports (_self); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Setup unsolicited handlers which should be always on */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* The Ericsson modems always have a free AT command port, so we + * don't need to flash the ports when disconnecting to get back to + * command mode. F5521gw R2A07 resets port properties like echo when + * flashed, leading to confusion. bgo #650740 + */ + g_object_set (G_OBJECT (ports[i]), + MM_PORT_SERIAL_FLASH_OK, FALSE, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->emrdy_regex, + (MMPortSerialAtUnsolicitedMsgFn)emrdy_received, + self, + NULL); + + /* Several unsolicited messages to always ignore... */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->pacsp_regex, + NULL, NULL, NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->estksmenu_regex, + NULL, NULL, NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->estksms_regex, + NULL, NULL, NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->emwi_regex, + NULL, NULL, NULL); + } + + /* Now reset the unsolicited messages we'll handle when enabled */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), FALSE); + + /* NMEA GPS monitoring */ + gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_data_port) { + /* make sure GPS is stopped incase it was left enabled */ + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + "AT*E2GPSCTL=0", + 3, FALSE, FALSE, NULL, NULL, NULL); + /* Add handler for the NMEA traces */ + mm_port_serial_gps_add_trace_handler (gps_data_port, + (MMPortSerialGpsTraceFn)gps_trace_received, + self, NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemMbm * +mm_broadband_modem_mbm_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBM, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* MBM bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + NULL); +} + +static void +mm_broadband_modem_mbm_init (MMBroadbandModemMbm *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_MBM, + MMBroadbandModemMbmPrivate); + + /* Prepare regular expressions to setup */ + self->priv->e2nap_regex = g_regex_new ("\\r\\n\\*E2NAP: (\\d)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->e2nap_ext_regex = g_regex_new ("\\r\\n\\*E2NAP: (\\d),.*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->emrdy_regex = g_regex_new ("\\r\\n\\*EMRDY: \\d\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->pacsp_regex = g_regex_new ("\\r\\n\\+PACSP(\\d)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->estksmenu_regex = g_regex_new ("\\R\\*ESTKSMENU:.*\\R", + G_REGEX_RAW | G_REGEX_OPTIMIZE | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, G_REGEX_MATCH_NEWLINE_CRLF, NULL); + self->priv->estksms_regex = g_regex_new ("\\r\\n\\*ESTKSMS:.*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->emwi_regex = g_regex_new ("\\r\\n\\*EMWI: (\\d),(\\d).*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->erinfo_regex = g_regex_new ("\\r\\n\\*ERINFO:\\s*(\\d),(\\d),(\\d).*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + self->priv->mbm_mode = MBM_NETWORK_MODE_ANY; +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (object); + + g_regex_unref (self->priv->e2nap_regex); + g_regex_unref (self->priv->e2nap_ext_regex); + g_regex_unref (self->priv->emrdy_regex); + g_regex_unref (self->priv->pacsp_regex); + g_regex_unref (self->priv->estksmenu_regex); + g_regex_unref (self->priv->estksms_regex); + g_regex_unref (self->priv->emwi_regex); + g_regex_unref (self->priv->erinfo_regex); + + G_OBJECT_CLASS (mm_broadband_modem_mbm_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; + iface->create_sim = create_sim; + iface->create_sim_finish = create_sim_finish; + iface->modem_after_sim_unlock = modem_after_sim_unlock; + iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->reset = reset; + iface->reset_finish = reset_finish; + iface->factory_reset = factory_reset; + iface->factory_reset_finish = factory_reset_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; + iface->load_power_state = load_power_state; + iface->load_power_state_finish = load_power_state_finish; + iface->modem_power_up = modem_power_up; + iface->modem_power_up_finish = modem_power_up_finish; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish; + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = location_load_capabilities; + iface->load_capabilities_finish = location_load_capabilities_finish; + iface->enable_location_gathering = enable_location_gathering; + iface->enable_location_gathering_finish = enable_location_gathering_finish; + iface->disable_location_gathering = disable_location_gathering; + iface->disable_location_gathering_finish = disable_location_gathering_finish; +} + +static void +mm_broadband_modem_mbm_class_init (MMBroadbandModemMbmClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemMbmPrivate)); + + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; + broadband_modem_class->enabling_modem_init = enabling_modem_init; + broadband_modem_class->enabling_modem_init_finish = enabling_modem_init_finish; +} diff --git a/src/plugins/mbm/mm-broadband-modem-mbm.h b/src/plugins/mbm/mm-broadband-modem-mbm.h new file mode 100644 index 00000000..21eeaef5 --- /dev/null +++ b/src/plugins/mbm/mm-broadband-modem-mbm.h @@ -0,0 +1,58 @@ +/* -*- 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) 2008 - 2010 Ericsson AB + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + * Bjorn Runaker <bjorn.runaker@ericsson.com> + * Torgny Johansson <torgny.johansson@ericsson.com> + * Jonas Sjöquist <jonas.sjoquist@ericsson.com> + * Dan Williams <dcbw@redhat.com> + * Aleksander Morgado <aleksander@lanedo.com> + */ + +#ifndef MM_BROADBAND_MODEM_MBM_H +#define MM_BROADBAND_MODEM_MBM_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_MBM (mm_broadband_modem_mbm_get_type ()) +#define MM_BROADBAND_MODEM_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbm)) +#define MM_BROADBAND_MODEM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbmClass)) +#define MM_IS_BROADBAND_MODEM_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBM)) +#define MM_IS_BROADBAND_MODEM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBM)) +#define MM_BROADBAND_MODEM_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbmClass)) + +typedef struct _MMBroadbandModemMbm MMBroadbandModemMbm; +typedef struct _MMBroadbandModemMbmClass MMBroadbandModemMbmClass; +typedef struct _MMBroadbandModemMbmPrivate MMBroadbandModemMbmPrivate; + +struct _MMBroadbandModemMbm { + MMBroadbandModem parent; + MMBroadbandModemMbmPrivate *priv; +}; + +struct _MMBroadbandModemMbmClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_mbm_get_type (void); + +MMBroadbandModemMbm *mm_broadband_modem_mbm_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_MBM_H */ diff --git a/src/plugins/mbm/mm-modem-helpers-mbm.c b/src/plugins/mbm/mm-modem-helpers-mbm.c new file mode 100644 index 00000000..846cc4d6 --- /dev/null +++ b/src/plugins/mbm/mm-modem-helpers-mbm.c @@ -0,0 +1,337 @@ +/* -*- 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) 2012 Google, Inc. + * Copyright (C) 2012 - 2013 Aleksander Morgado <aleksander@gnu.org> + * Copyright (C) 2014 Dan Williams <dcbw@redhat.com> + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" + +/*****************************************************************************/ +/* *E2IPCFG response parser */ + +static gboolean +validate_address (int family, const char *addr) +{ + struct in6_addr tmp6 = IN6ADDR_ANY_INIT; + + if (inet_pton (family, addr, (void *) &tmp6) != 1) +{ +g_message ("%s: famil '%s'", __func__, addr); + return FALSE; +} + if ((family == AF_INET6) && IN6_IS_ADDR_UNSPECIFIED (&tmp6)) + return FALSE; + return TRUE; +} + +#define E2IPCFG_TAG "*E2IPCFG" + +gboolean +mm_mbm_parse_e2ipcfg_response (const gchar *response, + MMBearerIpConfig **out_ip4_config, + MMBearerIpConfig **out_ip6_config, + GError **error) +{ + MMBearerIpConfig **ip_config = NULL; + gboolean got_address = FALSE; + gboolean got_gw = FALSE; + gboolean got_dns = FALSE; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *match_error = NULL; + gchar *dns[3] = { 0 }; + guint dns_idx = 0; + int family = AF_INET; + MMBearerIpMethod method = MM_BEARER_IP_METHOD_STATIC; + + g_return_val_if_fail (out_ip4_config, FALSE); + g_return_val_if_fail (out_ip6_config, FALSE); + + if (!response || !g_str_has_prefix (response, E2IPCFG_TAG)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing " E2IPCFG_TAG " prefix"); + return FALSE; + } + + response = mm_strip_tag (response, "*E2IPCFG: "); + + if (strchr (response, ':')) { + family = AF_INET6; + ip_config = out_ip6_config; + method = MM_BEARER_IP_METHOD_DHCP; + } else if (strchr (response, '.')) { + family = AF_INET; + ip_config = out_ip4_config; + method = MM_BEARER_IP_METHOD_STATIC; + } else { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to detect " E2IPCFG_TAG " address family"); + return FALSE; + } + + /* *E2IPCFG: (1,<IP>)(2,<gateway>)(3,<DNS>)(3,<DNS>) + * + * *E2IPCFG: (1,"46.157.32.246")(2,"46.157.32.243")(3,"193.213.112.4")(3,"130.67.15.198") + * *E2IPCFG: (1,"fe80:0000:0000:0000:0000:0000:e537:1801")(3,"2001:4600:0004:0fff:0000:0000:0000:0054")(3,"2001:4600:0004:1fff:0000:0000:0000:0054") + * *E2IPCFG: (1,"fe80:0000:0000:0000:0000:0027:b7fe:9401")(3,"fd00:976a:0000:0000:0000:0000:0000:0009") + */ + r = g_regex_new ("\\((\\d),\"([0-9a-fA-F.:]+)\"\\)", 0, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse " E2IPCFG_TAG " results: "); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match " E2IPCFG_TAG " reply"); + } + return FALSE; + } + + *ip_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (*ip_config, method); + while (g_match_info_matches (match_info)) { + g_autofree gchar *id = NULL; + g_autofree gchar *str = NULL; + + id = g_match_info_fetch (match_info, 1); + str = g_match_info_fetch (match_info, 2); + + switch (atoi (id)) { + case 1: + if (validate_address (family, str)) { + mm_bearer_ip_config_set_address (*ip_config, str); + mm_bearer_ip_config_set_prefix (*ip_config, (family == AF_INET6) ? 64 : 28); + got_address = TRUE; + } + break; + case 2: + if ((family == AF_INET) && validate_address (family, str)) { + mm_bearer_ip_config_set_gateway (*ip_config, str); + got_gw = TRUE; + } + break; + case 3: + if (validate_address (family, str)) { + dns[dns_idx++] = g_strdup (str); + got_dns = TRUE; + } + break; + default: + break; + } + g_match_info_next (match_info, NULL); + } + + if (got_dns) { + mm_bearer_ip_config_set_dns (*ip_config, (const gchar **) dns); + g_free (dns[0]); + g_free (dns[1]); + } + + if (!got_address || (family == AF_INET && !got_gw)) { + g_object_unref (*ip_config); + *ip_config = NULL; + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Got incomplete IP configuration from " E2IPCFG_TAG); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +#define CFUN_TAG "+CFUN:" + +static void +add_supported_mode (guint mode, + gpointer log_object, + guint32 *mask) +{ + g_assert (mask); + if (mode >= 32) + mm_obj_warn (log_object, "ignored unexpected mode in +CFUN match: %d", mode); + else + *mask |= (1 << mode); +} + +gboolean +mm_mbm_parse_cfun_test (const gchar *response, + gpointer log_object, + guint32 *supported_mask, + GError **error) +{ + gchar **groups; + guint32 mask = 0; + + g_assert (supported_mask); + + if (!response || !g_str_has_prefix (response, CFUN_TAG)) { + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Missing " CFUN_TAG " prefix"); + return FALSE; + } + + /* + * AT+CFUN=? + * +CFUN: (0,1,4-6),(0,1) + * OK + */ + + /* Strip tag from response */ + response = mm_strip_tag (response, CFUN_TAG); + + /* Split response in (groups) */ + groups = mm_split_string_groups (response); + + /* First group is the one listing supported modes */ + if (groups && groups[0]) { + gchar **supported_modes; + + supported_modes = g_strsplit_set (groups[0], ", ", -1); + if (supported_modes) { + guint i; + + for (i = 0; supported_modes[i]; i++) { + gchar *separator; + guint mode; + + if (!supported_modes[i][0]) + continue; + + /* Check if this is a range that's being given to us */ + separator = strchr (supported_modes[i], '-'); + if (separator) { + gchar *first_str; + gchar *last_str; + guint first; + guint last; + + *separator = '\0'; + first_str = supported_modes[i]; + last_str = separator + 1; + + if (!mm_get_uint_from_str (first_str, &first)) + mm_obj_warn (log_object, "couldn't match range start: '%s'", first_str); + else if (!mm_get_uint_from_str (last_str, &last)) + mm_obj_warn (log_object, "couldn't match range stop: '%s'", last_str); + else if (first >= last) + mm_obj_warn (log_object, "couldn't match range: wrong first '%s' and last '%s' items", first_str, last_str); + else { + for (mode = first; mode <= last; mode++) + add_supported_mode (mode, log_object, &mask); + } + } else { + if (!mm_get_uint_from_str (supported_modes[i], &mode)) + mm_obj_warn (log_object, "couldn't match mode: '%s'", supported_modes[i]); + else + add_supported_mode (mode, log_object, &mask); + } + } + + g_strfreev (supported_modes); + } + } + g_strfreev (groups); + + if (mask) + *supported_mask = mask; + return !!mask; +} + +/*****************************************************************************/ +/* AT+CFUN? response parsers */ + +gboolean +mm_mbm_parse_cfun_query_power_state (const gchar *response, + MMModemPowerState *out_state, + GError **error) +{ + guint state; + + if (!mm_3gpp_parse_cfun_query_response (response, &state, error)) + return FALSE; + + switch (state) { + case MBM_NETWORK_MODE_OFFLINE: + *out_state = MM_MODEM_POWER_STATE_OFF; + return TRUE; + case MBM_NETWORK_MODE_LOW_POWER: + *out_state = MM_MODEM_POWER_STATE_LOW; + return TRUE; + case MBM_NETWORK_MODE_ANY: + case MBM_NETWORK_MODE_2G: + case MBM_NETWORK_MODE_3G: + *out_state = MM_MODEM_POWER_STATE_ON; + return TRUE; + default: + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown +CFUN pòwer state: '%u'", state); + return FALSE; + } +} + +gboolean +mm_mbm_parse_cfun_query_current_modes (const gchar *response, + MMModemMode *allowed, + gint *mbm_mode, + GError **error) +{ + guint state; + + g_assert (mbm_mode); + g_assert (allowed); + + if (!mm_3gpp_parse_cfun_query_response (response, &state, error)) + return FALSE; + + switch (state) { + case MBM_NETWORK_MODE_OFFLINE: + case MBM_NETWORK_MODE_LOW_POWER: + /* Do not update mbm_mode */ + *allowed = MM_MODEM_MODE_NONE; + return TRUE; + case MBM_NETWORK_MODE_2G: + *mbm_mode = MBM_NETWORK_MODE_2G; + *allowed = MM_MODEM_MODE_2G; + return TRUE; + case MBM_NETWORK_MODE_3G: + *mbm_mode = MBM_NETWORK_MODE_3G; + *allowed = MM_MODEM_MODE_3G; + return TRUE; + case MBM_NETWORK_MODE_ANY: + /* Do not update mbm_mode */ + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + return TRUE; + default: + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown +CFUN current mode: '%u'", state); + return FALSE; + } +} diff --git a/src/plugins/mbm/mm-modem-helpers-mbm.h b/src/plugins/mbm/mm-modem-helpers-mbm.h new file mode 100644 index 00000000..3e3bf57a --- /dev/null +++ b/src/plugins/mbm/mm-modem-helpers-mbm.h @@ -0,0 +1,51 @@ +/* -*- 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) 2014 Dan Williams <dcbw@redhat.com> + */ + +#ifndef MM_MODEM_HELPERS_MBM_H +#define MM_MODEM_HELPERS_MBM_H + +#include "glib.h" + +/* *E2IPCFG response parser */ +gboolean mm_mbm_parse_e2ipcfg_response (const gchar *response, + MMBearerIpConfig **out_ip4_config, + MMBearerIpConfig **out_ip6_config, + GError **error); + +typedef enum { + MBM_NETWORK_MODE_OFFLINE = 0, + MBM_NETWORK_MODE_ANY = 1, + MBM_NETWORK_MODE_LOW_POWER = 4, + MBM_NETWORK_MODE_2G = 5, + MBM_NETWORK_MODE_3G = 6, +} MbmNetworkMode; + +/* AT+CFUN=? test parser + * Returns a bitmask, bit index set for the supported modes reported */ +gboolean mm_mbm_parse_cfun_test (const gchar *response, + gpointer log_object, + guint32 *supported_mask, + GError **error); + +/* AT+CFUN? response parsers */ +gboolean mm_mbm_parse_cfun_query_power_state (const gchar *response, + MMModemPowerState *out_state, + GError **error); +gboolean mm_mbm_parse_cfun_query_current_modes (const gchar *response, + MMModemMode *allowed, + gint *mbm_mode, + GError **error); + +#endif /* MM_MODEM_HELPERS_MBM_H */ diff --git a/src/plugins/mbm/mm-plugin-mbm.c b/src/plugins/mbm/mm-plugin-mbm.c new file mode 100644 index 00000000..6790d8a8 --- /dev/null +++ b/src/plugins/mbm/mm-plugin-mbm.c @@ -0,0 +1,101 @@ +/* -*- 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) 2008 Ericsson AB + * Copyright (C) 2012 Lanedo GmbH + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-mbm.h" +#include "mm-broadband-modem-mbm.h" + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#endif + +G_DEFINE_TYPE (MMPluginMbm, mm_plugin_mbm, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered Ericsson modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_mbm_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const gchar *udev_tags[] = { + "ID_MM_ERICSSON_MBM", + NULL + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_MBM, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + NULL)); +} + +static void +mm_plugin_mbm_init (MMPluginMbm *self) +{ +} + +static void +mm_plugin_mbm_class_init (MMPluginMbmClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/mbm/mm-plugin-mbm.h b/src/plugins/mbm/mm-plugin-mbm.h new file mode 100644 index 00000000..ac07d7e1 --- /dev/null +++ b/src/plugins/mbm/mm-plugin-mbm.h @@ -0,0 +1,43 @@ +/* -*- 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) 2008 Ericsson AB + * Copyright (C) 2012 Lanedo GmbH + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + */ + +#ifndef MM_PLUGIN_MBM_H +#define MM_PLUGIN_MBM_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_MBM (mm_plugin_mbm_get_type ()) +#define MM_PLUGIN_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_MBM, MMPluginMbm)) +#define MM_PLUGIN_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_MBM, MMPluginMbmClass)) +#define MM_IS_PLUGIN_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_MBM)) +#define MM_IS_PLUGIN_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_MBM)) +#define MM_PLUGIN_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_MBM, MMPluginMbmClass)) + +typedef struct { + MMPlugin parent; +} MMPluginMbm; + +typedef struct { + MMPluginClass parent; +} MMPluginMbmClass; + +GType mm_plugin_mbm_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_MBM_H */ diff --git a/src/plugins/mbm/mm-sim-mbm.c b/src/plugins/mbm/mm-sim-mbm.c new file mode 100644 index 00000000..d3f73954 --- /dev/null +++ b/src/plugins/mbm/mm-sim-mbm.c @@ -0,0 +1,242 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-base-modem-at.h" +#include "mm-sim-mbm.h" + +G_DEFINE_TYPE (MMSimMbm, mm_sim_mbm, MM_TYPE_BASE_SIM) + +/*****************************************************************************/ +/* SEND PIN/PUK (Generic implementation) */ + +typedef struct { + MMBaseModem *modem; + guint retries; +} SendPinPukContext; + +static void +send_pin_puk_context_free (SendPinPukContext *ctx) +{ + g_object_unref (ctx->modem); + g_slice_free (SendPinPukContext, ctx); +} + +static gboolean +common_send_pin_puk_finish (MMBaseSim *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void wait_for_unlocked_status (GTask *task); + +static void +cpin_query_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + + const gchar *result; + + result = mm_base_modem_at_command_finish (modem, res, NULL); + if (result && strstr (result, "READY")) { + /* All done! */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Need to recheck */ + wait_for_unlocked_status (task); +} + +static gboolean +cpin_query_cb (GTask *task) +{ + SendPinPukContext *ctx; + + ctx = g_task_get_task_data (task); + mm_base_modem_at_command (ctx->modem, + "+CPIN?", + 20, + FALSE, + (GAsyncReadyCallback)cpin_query_ready, + task); + return G_SOURCE_REMOVE; +} + +static void +wait_for_unlocked_status (GTask *task) +{ + MMSimMbm *self; + SendPinPukContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* Oops... :/ */ + if (ctx->retries == 0) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "PIN was sent but modem didn't report unlocked"); + g_object_unref (task); + return; + } + + /* Check status */ + ctx->retries--; + mm_obj_dbg (self, "scheduling lock state check..."); + g_timeout_add_seconds (1, (GSourceFunc)cpin_query_cb, task); +} + +static void +send_pin_puk_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + SendPinPukContext *ctx; + GError *error = NULL; + + mm_base_modem_at_command_finish (modem, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* No explicit error sending the PIN/PUK, now check status until we have the + * expected lock status */ + ctx = g_task_get_task_data (task); + ctx->retries = 3; + wait_for_unlocked_status (task); +} + +static void +common_send_pin_puk (MMBaseSim *self, + const gchar *pin, + const gchar *puk, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SendPinPukContext *ctx; + GTask *task; + gchar *command; + + ctx = g_slice_new (SendPinPukContext); + g_object_get (self, + MM_BASE_SIM_MODEM, &ctx->modem, + NULL); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)send_pin_puk_context_free); + + command = (puk ? + g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin) : + g_strdup_printf ("+CPIN=\"%s\"", pin)); + mm_base_modem_at_command (ctx->modem, + command, + 3, + FALSE, + (GAsyncReadyCallback)send_pin_puk_ready, + task); + g_free (command); +} + +static void +send_puk (MMBaseSim *self, + const gchar *puk, + const gchar *new_pin, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_send_pin_puk (self, new_pin, puk, callback, user_data); +} + +static void +send_pin (MMBaseSim *self, + const gchar *pin, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_send_pin_puk (self, pin, NULL, callback, user_data); +} + +/*****************************************************************************/ + +MMBaseSim * +mm_sim_mbm_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *sim; + + source = g_async_result_get_source_object (res); + sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!sim) + return NULL; + + /* Only export valid SIMs */ + mm_base_sim_export (MM_BASE_SIM (sim)); + + return MM_BASE_SIM (sim); +} + +void +mm_sim_mbm_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (MM_TYPE_SIM_MBM, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_SIM_MODEM, modem, + "active", TRUE, /* by default always active */ + NULL); +} + +static void +mm_sim_mbm_init (MMSimMbm *self) +{ +} + +static void +mm_sim_mbm_class_init (MMSimMbmClass *klass) +{ + MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass); + + base_sim_class->send_pin = send_pin; + base_sim_class->send_pin_finish = common_send_pin_puk_finish; + base_sim_class->send_puk = send_puk; + base_sim_class->send_puk_finish = common_send_pin_puk_finish; +} diff --git a/src/plugins/mbm/mm-sim-mbm.h b/src/plugins/mbm/mm-sim-mbm.h new file mode 100644 index 00000000..1d843242 --- /dev/null +++ b/src/plugins/mbm/mm-sim-mbm.h @@ -0,0 +1,51 @@ +/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_SIM_MBM_H +#define MM_SIM_MBM_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-sim.h" + +#define MM_TYPE_SIM_MBM (mm_sim_mbm_get_type ()) +#define MM_SIM_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_MBM, MMSimMbm)) +#define MM_SIM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_MBM, MMSimMbmClass)) +#define MM_IS_SIM_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_MBM)) +#define MM_IS_SIM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_MBM)) +#define MM_SIM_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_MBM, MMSimMbmClass)) + +typedef struct _MMSimMbm MMSimMbm; +typedef struct _MMSimMbmClass MMSimMbmClass; + +struct _MMSimMbm { + MMBaseSim parent; +}; + +struct _MMSimMbmClass { + MMBaseSimClass parent; +}; + +GType mm_sim_mbm_get_type (void); + +void mm_sim_mbm_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_sim_mbm_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_SIM_MBM_H */ diff --git a/src/plugins/mbm/tests/test-modem-helpers-mbm.c b/src/plugins/mbm/tests/test-modem-helpers-mbm.c new file mode 100644 index 00000000..4169140a --- /dev/null +++ b/src/plugins/mbm/tests/test-modem-helpers-mbm.c @@ -0,0 +1,268 @@ +/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org> + * Copyright (C) 2014 Dan Williams <dcbw@redhat.com> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" + +/*****************************************************************************/ +/* Test *E2IPCFG responses */ + +typedef struct { + const gchar *str; + + /* IPv4 */ + const gchar *ipv4_addr; + const gchar *ipv4_gw; + const gchar *ipv4_dns1; + const gchar *ipv4_dns2; + + /* IPv6 */ + const gchar *ipv6_addr; + const gchar *ipv6_dns1; + const gchar *ipv6_dns2; +} E2ipcfgTest; + +static const E2ipcfgTest tests[] = { + { "*E2IPCFG: (1,\"46.157.32.246\")(2,\"46.157.32.243\")(3,\"193.213.112.4\")(3,\"130.67.15.198\")\r\n", + "46.157.32.246", "46.157.32.243", "193.213.112.4", "130.67.15.198", + NULL, NULL }, + + { "*E2IPCFG: (1,\"fe80:0000:0000:0000:0000:0000:e537:1801\")(3,\"2001:4600:0004:0fff:0000:0000:0000:0054\")(3,\"2001:4600:0004:1fff:0000:0000:0000:0054\")\r\n", + NULL, NULL, NULL, NULL, + "fe80:0000:0000:0000:0000:0000:e537:1801", "2001:4600:0004:0fff:0000:0000:0000:0054", "2001:4600:0004:1fff:0000:0000:0000:0054" }, + + { "*E2IPCFG: (1,\"fe80:0000:0000:0000:0000:0027:b7fe:9401\")(3,\"fd00:976a:0000:0000:0000:0000:0000:0009\")\r\n", + NULL, NULL, NULL, NULL, + "fe80:0000:0000:0000:0000:0027:b7fe:9401", "fd00:976a:0000:0000:0000:0000:0000:0009", NULL }, + + { NULL } +}; + +static void +test_e2ipcfg (void) +{ + guint i; + + for (i = 0; tests[i].str; i++) { + gboolean success; + GError *error = NULL; + MMBearerIpConfig *ipv4 = NULL; + MMBearerIpConfig *ipv6 = NULL; + const gchar **dns; + guint dnslen; + + success = mm_mbm_parse_e2ipcfg_response (tests[i].str, &ipv4, &ipv6, &error); + g_assert_no_error (error); + g_assert (success); + + /* IPv4 */ + if (tests[i].ipv4_addr) { + g_assert (ipv4); + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv4), ==, MM_BEARER_IP_METHOD_STATIC); + g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv4), ==, tests[i].ipv4_addr); + g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv4), ==, 28); + g_assert_cmpstr (mm_bearer_ip_config_get_gateway (ipv4), ==, tests[i].ipv4_gw); + + dns = mm_bearer_ip_config_get_dns (ipv4); + g_assert (dns); + dnslen = g_strv_length ((gchar **) dns); + if (tests[i].ipv4_dns2 != NULL) + g_assert_cmpint (dnslen, ==, 2); + else + g_assert_cmpint (dnslen, ==, 1); + g_assert_cmpstr (dns[0], ==, tests[i].ipv4_dns1); + g_assert_cmpstr (dns[1], ==, tests[i].ipv4_dns2); + g_object_unref (ipv4); + } else + g_assert (ipv4 == NULL); + + /* IPv6 */ + if (tests[i].ipv6_addr) { + struct in6_addr a6; + g_assert (ipv6); + + g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv6), ==, tests[i].ipv6_addr); + g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv6), ==, 64); + + g_assert (inet_pton (AF_INET6, mm_bearer_ip_config_get_address (ipv6), &a6)); + if (IN6_IS_ADDR_LINKLOCAL (&a6)) + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_DHCP); + else + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_STATIC); + + dns = mm_bearer_ip_config_get_dns (ipv6); + g_assert (dns); + dnslen = g_strv_length ((gchar **) dns); + if (tests[i].ipv6_dns2 != NULL) + g_assert_cmpint (dnslen, ==, 2); + else + g_assert_cmpint (dnslen, ==, 1); + g_assert_cmpstr (dns[0], ==, tests[i].ipv6_dns1); + g_assert_cmpstr (dns[1], ==, tests[i].ipv6_dns2); + g_object_unref (ipv6); + } else + g_assert (ipv6 == NULL); + } +} + +/*****************************************************************************/ +/* Test +CFUN test responses */ + +#define MAX_MODES 32 + +typedef struct { + const gchar *str; + guint32 expected_mask; +} CfunTest; + +static const CfunTest cfun_tests[] = { + { + "+CFUN: (0,1,4-6),(1-0)\r\n", + ((1 << MBM_NETWORK_MODE_OFFLINE) | + (1 << MBM_NETWORK_MODE_ANY) | + (1 << MBM_NETWORK_MODE_LOW_POWER) | + (1 << MBM_NETWORK_MODE_2G) | + (1 << MBM_NETWORK_MODE_3G)) + }, + { + "+CFUN: (0,1,4-6)\r\n", + ((1 << MBM_NETWORK_MODE_OFFLINE) | + (1 << MBM_NETWORK_MODE_ANY) | + (1 << MBM_NETWORK_MODE_LOW_POWER) | + (1 << MBM_NETWORK_MODE_2G) | + (1 << MBM_NETWORK_MODE_3G)) + }, + { + "+CFUN: (0,1,4)\r\n", + ((1 << MBM_NETWORK_MODE_OFFLINE) | + (1 << MBM_NETWORK_MODE_ANY) | + (1 << MBM_NETWORK_MODE_LOW_POWER)) + }, + { + "+CFUN: (0,1)\r\n", + ((1 << MBM_NETWORK_MODE_OFFLINE) | + (1 << MBM_NETWORK_MODE_ANY)) + }, +}; + +static void +test_cfun_test (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (cfun_tests); i++) { + guint32 mask; + gboolean success; + GError *error = NULL; + + success = mm_mbm_parse_cfun_test (cfun_tests[i].str, NULL, &mask, &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (mask, ==, cfun_tests[i].expected_mask); + } +} + +/*****************************************************************************/ + +typedef struct { + const gchar *str; + MMModemPowerState state; +} CfunQueryPowerStateTest; + +static const CfunQueryPowerStateTest cfun_query_power_state_tests[] = { + { "+CFUN: 0", MM_MODEM_POWER_STATE_OFF }, + { "+CFUN: 1", MM_MODEM_POWER_STATE_ON }, + { "+CFUN: 4", MM_MODEM_POWER_STATE_LOW }, + { "+CFUN: 5", MM_MODEM_POWER_STATE_ON }, + { "+CFUN: 6", MM_MODEM_POWER_STATE_ON }, +}; + +static void +test_cfun_query_power_state (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (cfun_query_power_state_tests); i++) { + GError *error = NULL; + gboolean success; + MMModemPowerState state; + + success = mm_mbm_parse_cfun_query_power_state (cfun_query_power_state_tests[i].str, &state, &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (cfun_query_power_state_tests[i].state, ==, state); + } +} + +typedef struct { + const gchar *str; + MMModemMode allowed; + gint mbm_mode; +} CfunQueryCurrentModeTest; + +static const CfunQueryCurrentModeTest cfun_query_current_mode_tests[] = { + { "+CFUN: 0", MM_MODEM_MODE_NONE, -1 }, + { "+CFUN: 1", MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, -1 }, + { "+CFUN: 4", MM_MODEM_MODE_NONE, -1 }, + { "+CFUN: 5", MM_MODEM_MODE_2G, MBM_NETWORK_MODE_2G }, + { "+CFUN: 6", MM_MODEM_MODE_3G, MBM_NETWORK_MODE_3G }, +}; + +static void +test_cfun_query_current_modes (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (cfun_query_current_mode_tests); i++) { + GError *error = NULL; + gboolean success; + MMModemMode allowed = MM_MODEM_MODE_NONE; + gint mbm_mode = -1; + + success = mm_mbm_parse_cfun_query_current_modes (cfun_query_current_mode_tests[i].str, &allowed, &mbm_mode, &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (cfun_query_current_mode_tests[i].allowed, ==, allowed); + g_assert_cmpint (cfun_query_current_mode_tests[i].mbm_mode, ==, mbm_mode); + } +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/mbm/e2ipcfg", test_e2ipcfg); + g_test_add_func ("/MM/mbm/cfun/test", test_cfun_test); + g_test_add_func ("/MM/mbm/cfun/query/power-state", test_cfun_query_power_state); + g_test_add_func ("/MM/mbm/cfun/query/current-modes", test_cfun_query_current_modes); + + return g_test_run (); +} diff --git a/src/plugins/meson.build b/src/plugins/meson.build new file mode 100644 index 00000000..9e081beb --- /dev/null +++ b/src/plugins/meson.build @@ -0,0 +1,1027 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2021 Iñigo Martinez <inigomartinez@gmail.com> + +symbol_map = plugins_dir / 'symbol.map' +ldflags = cc.get_supported_link_arguments('-Wl,--version-script,@0@'.format(symbol_map)) + +# common service test support +plugins_common_test_dep = [] +if enable_tests + sources = files( + 'tests/test-fixture.c', + 'tests/test-helpers.c', + 'tests/test-port-context.c', + ) + + deps = [ + libhelpers_dep, + libmm_test_generated_dep + ] + + libmm_test_common = library( + 'mm-test-common', + sources: sources, + include_directories: top_inc, + dependencies: deps + [gio_unix_dep], + c_args: '-DTEST_SERVICES="@0@"'.format(build_root / 'data/tests'), + ) + + libmm_test_common_dep = declare_dependency( + include_directories: 'tests', + dependencies: deps, + link_with: libmm_test_common, + ) + + plugins_common_test_dep += [ libmm_test_common_dep ] +endif + +# plugins +plugins = {} +plugins_data = [] +plugins_udev_rules = [] +plugins_test_udev_rules_dir_c_args = [] +plugins_test_keyfile_c_args = [] + +# never include static libs as deps when building +# plugins or shared utils modules +plugins_incs = [ + top_inc, + src_inc, + kerneldevice_inc, +] + +plugins_deps = [libmm_glib_dep] + +if enable_mbim + plugins_deps += mbim_glib_dep +endif + +if enable_qmi + plugins_deps += qmi_glib_dep +endif + +# common Fibocom support library (MBIM only) +if plugins_shared['fibocom'] + fibocom_inc = include_directories('fibocom') + + c_args = '-DMM_MODULE_NAME="shared-fibocom"' + + sources = files( + 'fibocom/mm-shared.c', + 'fibocom/mm-shared-fibocom.c', + ) + + plugins += {'shared-fibocom': { + 'plugin': false, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': c_args}, + }} +endif + +# Common Foxconn modem support library (MBIM only) +if plugins_shared['foxconn'] + foxconn_inc = include_directories('foxconn') + + sources = files( + 'foxconn/mm-broadband-modem-mbim-foxconn.c', + 'foxconn/mm-shared.c', + ) + + c_args = [ + '-DMM_MODULE_NAME="shared-foxconn"', + '-DPKGDATADIR="@0@"'.format(mm_pkgdatadir), + ] + + plugins += {'shared-foxconn': { + 'plugin': false, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': c_args}, + }} +endif + +# common icera support +if plugins_shared['icera'] + icera_inc = include_directories('icera') + + common_c_args = '-DMM_MODULE_NAME="shared-icera"' + + sources = files( + 'icera/mm-broadband-bearer-icera.c', + 'icera/mm-broadband-modem-icera.c', + 'icera/mm-shared.c', + ) + + plugins += {'shared-icera': { + 'plugin': false, + 'helper': {'sources': files('icera/mm-modem-helpers-icera.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'test': {'sources': files('icera/tests/test-modem-helpers-icera.c'), 'include_directories': plugins_incs + [icera_inc], 'dependencies': libhelpers_dep}, + }} +endif + +# common novatel support +if plugins_shared['novatel'] + novatel_inc = include_directories('novatel') + + sources = files( + 'novatel/mm-broadband-modem-novatel.c', + 'novatel/mm-common-novatel.c', + 'novatel/mm-shared.c', + ) + + plugins += {'shared-novatel': { + 'plugin': false, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="shared-novatel"'}, + }} +endif + +# common option support +if plugins_shared['option'] + sources = files( + 'option/mm-broadband-modem-option.c', + 'option/mm-shared.c', + 'option/mm-shared-option.c', + 'option/mm-sim-option.c', + ) + + plugins += {'shared-option': { + 'plugin': false, + 'module': {'sources': sources, 'include_directories': plugins_incs}, + }} +endif + +# common sierra support +if plugins_shared['sierra'] + sierra_inc = include_directories('sierra') + + common_c_args = '-DMM_MODULE_NAME="shared-sierra"' + + sources = files( + 'sierra/mm-broadband-bearer-sierra.c', + 'sierra/mm-broadband-modem-sierra.c', + 'sierra/mm-common-sierra.c', + 'sierra/mm-shared.c', + 'sierra/mm-sim-sierra.c', + ) + + plugins += {'shared-sierra': { + 'plugin': false, + 'helper': {'sources': files('sierra/mm-modem-helpers-sierra.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'test': {'sources': files('sierra/tests/test-modem-helpers-sierra.c'), 'include_directories': sierra_inc, 'dependencies': libhelpers_dep}, + }} +endif + +# common telit support +if plugins_shared['telit'] + telit_inc = include_directories('telit') + + common_c_args = '-DMM_MODULE_NAME="shared-telit"' + + headers = files('telit/mm-modem-helpers-telit.h') + + sources = files( + 'telit/mm-broadband-modem-telit.c', + 'telit/mm-common-telit.c', + 'telit/mm-shared.c', + 'telit/mm-shared-telit.c', + ) + + enums_types = 'mm-telit-enums-types' + + sources += gnome.mkenums( + enums_types + '.c', + sources: headers, + c_template: build_aux_dir / enums_types + '.c.template', + fhead: '#include "mm-telit-enums-types.h"', + ) + + sources += gnome.mkenums( + enums_types + '.h', + sources: headers, + h_template: build_aux_dir / enums_types + '.h.template', + fhead: '#include "mm-modem-helpers-telit.h"\n#ifndef __MM_TELIT_ENUMS_TYPES_H__\n#define __MM_TELIT_ENUMS_TYPES_H__\n', + ftail: '#endif /* __MM_TELIT_ENUMS_TYPES_H__ */\n', + ) + + if enable_mbim + sources += files('telit/mm-broadband-modem-mbim-telit.c') + endif + + plugins += {'shared-telit': { + 'plugin': false, + 'helper': {'sources': files('telit/mm-modem-helpers-telit.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs + [telit_inc], 'c_args': common_c_args}, + 'test': {'sources': files('telit/tests/test-mm-modem-helpers-telit.c'), 'include_directories': telit_inc, 'dependencies': plugins_common_test_dep}, + }} +endif + +# common xmm support +if plugins_shared['xmm'] + xmm_inc = include_directories('xmm') + + common_c_args = '-DMM_MODULE_NAME="shared-xmm"' + + sources = files( + 'xmm/mm-broadband-modem-xmm.c', + 'xmm/mm-shared.c', + 'xmm/mm-shared-xmm.c', + ) + + if enable_mbim + sources += files('xmm/mm-broadband-modem-mbim-xmm.c') + endif + + plugins += {'shared-xmm': { + 'plugin': false, + 'helper': {'sources': files('xmm/mm-modem-helpers-xmm.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'test': {'sources': files('xmm/tests/test-modem-helpers-xmm.c'), 'include_directories': xmm_inc, 'dependencies': libhelpers_dep}, + }} +endif + +# plugin: altair lte +if plugins_options['altair-lte'] + common_c_args = '-DMM_MODULE_NAME="altair-lte"' + + sources = files( + 'altair/mm-broadband-bearer-altair-lte.c', + 'altair/mm-broadband-modem-altair-lte.c', + 'altair/mm-plugin-altair-lte.c', + ) + + plugins += {'plugin-altair-lte': { + 'plugin': true, + 'helper': {'sources': files('altair/mm-modem-helpers-altair-lte.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'test': {'sources': files('altair/tests/test-modem-helpers-altair-lte.c'), 'include_directories': include_directories('altair'), 'dependencies': libhelpers_dep}, + }} +endif + +# plugin: anydata +if plugins_options['anydata'] + sources = files( + 'anydata/mm-broadband-modem-anydata.c', + 'anydata/mm-plugin-anydata.c', + ) + + plugins += {'plugin-anydata': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="anydata"'}, + }} +endif + +# plugin: broadmobi +if plugins_options['broadmobi'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_BROADMOBI="@0@"'.format(plugins_dir / 'broadmobi')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + plugins += {'plugin-broadmobi': { + 'plugin': true, + 'module': {'sources': files('broadmobi/mm-plugin-broadmobi.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="broadmobi"']}, + }} + + plugins_udev_rules += files('broadmobi/77-mm-broadmobi-port-types.rules') +endif + +# plugin: cinterion (previously siemens) +if plugins_options['cinterion'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_CINTERION="@0@"'.format(plugins_dir / 'cinterion')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="cinterion"'] + + sources = files( + 'cinterion/mm-broadband-bearer-cinterion.c', + 'cinterion/mm-broadband-modem-cinterion.c', + 'cinterion/mm-plugin-cinterion.c', + 'cinterion/mm-shared-cinterion.c', + ) + + if enable_qmi + sources += files('cinterion/mm-broadband-modem-qmi-cinterion.c') + endif + + if enable_mbim + sources += files('cinterion/mm-broadband-modem-mbim-cinterion.c') + endif + + plugins += {'plugin-cinterion': { + 'plugin': true, + 'helper': {'sources': files('cinterion/mm-modem-helpers-cinterion.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'test': {'sources': files('cinterion/tests/test-modem-helpers-cinterion.c'), 'include_directories': plugins_incs + [include_directories('cinterion')], 'dependencies': libport_dep}, + }} + + plugins_udev_rules += files('cinterion/77-mm-cinterion-port-types.rules') +endif + +# plugin: dell +if plugins_options['dell'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_DELL="@0@"'.format(plugins_dir / 'dell')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + incs = plugins_incs + [ + novatel_inc, + sierra_inc, + telit_inc, + xmm_inc, + ] + + if enable_mbim + incs += [foxconn_inc] + endif + + plugins += {'plugin-dell': { + 'plugin': true, + 'module': {'sources': files('dell/mm-plugin-dell.c'), 'include_directories': incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="dell"']}, + }} + + plugins_udev_rules += files('dell/77-mm-dell-port-types.rules') +endif + +# plugin: dlink +if plugins_options['dlink'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_DLINK="@0@"'.format(plugins_dir / 'dlink')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + plugins += {'plugin-dlink': { + 'plugin': true, + 'module': {'sources': files('dlink/mm-plugin-dlink.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="d-link"']}, + }} + + plugins_udev_rules += files('dlink/77-mm-dlink-port-types.rules') +endif + +# plugin: fibocom +if plugins_options['fibocom'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_FIBOCOM="@0@"'.format(plugins_dir / 'fibocom')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + incs = plugins_incs + [xmm_inc] + + sources = files( + 'fibocom/mm-broadband-bearer-fibocom-ecm.c', + 'fibocom/mm-broadband-modem-fibocom.c', + 'fibocom/mm-plugin-fibocom.c', + ) + if enable_mbim + incs += [fibocom_inc] + + sources += files( + 'fibocom/mm-broadband-modem-mbim-xmm-fibocom.c', + 'fibocom/mm-broadband-modem-mbim-fibocom.c', + ) + endif + plugins += {'plugin-fibocom': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="fibocom"']}, + }} + + plugins_udev_rules += files('fibocom/77-mm-fibocom-port-types.rules') +endif + +# plugin: foxconn +if plugins_options['foxconn'] + foxconn_dir = plugins_dir / 'foxconn' + + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_FOXCONN="@0@"'.format(foxconn_dir)] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + test_keyfile_c_args = ['-DTESTKEYFILE_FOXCONN_T77W968="@0@"'.format(foxconn_dir / 'mm-foxconn-t77w968-carrier-mapping.conf')] + plugins_test_keyfile_c_args += test_keyfile_c_args + + plugins += {'plugin-foxconn': { + 'plugin': true, + 'module': {'sources': files('foxconn/mm-plugin-foxconn.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + test_keyfile_c_args + ['-DMM_MODULE_NAME="foxconn"']}, + }} + + plugins_data += files( + 'foxconn/mm-foxconn-t77w968-carrier-mapping.conf', + ) + plugins_udev_rules += files('foxconn/77-mm-foxconn-port-types.rules') +endif + +# plugin: generic +if plugins_options['generic'] + plugins += {'plugin-generic': { + 'plugin': true, + 'module': {'sources': files('generic/mm-plugin-generic.c'), 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="generic"'}, + 'test': {'sources': files('generic/tests/test-service-generic.c'), 'include_directories': include_directories('generic'), 'dependencies': plugins_common_test_dep, 'c_args': '-DCOMMON_GSM_PORT_CONF="@0@"'.format(plugins_dir / 'tests/gsm-port.conf')}, + }} +endif + +# plugin: gosuncn +if plugins_options['gosuncn'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_GOSUNCN="@0@"'.format(plugins_dir / 'gosuncn')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + plugins += {'plugin-gosuncn': { + 'plugin': true, + 'module': {'sources': files('gosuncn/mm-plugin-gosuncn.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="gosuncn"']}, + }} + + plugins_udev_rules += files('gosuncn/77-mm-gosuncn-port-types.rules') +endif + +# plugin: haier +if plugins_options['haier'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_HAIER="@0@"'.format(plugins_dir / 'haier')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + plugins += {'plugin-haier': { + 'plugin': true, + 'module': {'sources': files('haier/mm-plugin-haier.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="haier"']}, + }} + + plugins_udev_rules += files('haier/77-mm-haier-port-types.rules') +endif + +# plugin: huawei +if plugins_options['huawei'] + huawei_inc = include_directories('huawei') + + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_HUAWEI="@0@"'.format(plugins_dir / 'huawei')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="huawei"'] + + headers = files('huawei/mm-modem-helpers-huawei.h') + + sources = files( + 'huawei/mm-broadband-bearer-huawei.c', + 'huawei/mm-broadband-modem-huawei.c', + 'huawei/mm-plugin-huawei.c', + 'huawei/mm-sim-huawei.c', + ) + + enums_types = 'mm-huawei-enums-types' + + enums_sources = [] + enums_sources += gnome.mkenums( + enums_types + '.c', + sources: headers, + c_template: build_aux_dir / enums_types + '.c.template', + fhead: '#include "mm-huawei-enums-types.h"', + ) + + enums_sources += gnome.mkenums( + enums_types + '.h', + sources: headers, + h_template: build_aux_dir / enums_types + '.h.template', + fhead: '#include "mm-modem-helpers-huawei.h"\n#ifndef __MM_HUAWEI_ENUMS_TYPES_H__\n#define __MM_HUAWEI_ENUMS_TYPES_H__\n', + ftail: '#endif /* __MM_HUAWEI_ENUMS_TYPES_H__ */\n', + ) + + plugins += {'plugin-huawei': { + 'plugin': true, + 'helper': {'sources': files('huawei/mm-modem-helpers-huawei.c') + daemon_enums_sources, 'include_directories': plugins_incs + [huawei_inc], 'c_args': common_c_args}, + 'module': {'sources': sources + enums_sources + port_enums_sources + daemon_enums_sources, 'include_directories': plugins_incs + [huawei_inc], 'c_args': common_c_args}, + 'test': {'sources': files('huawei/tests/test-modem-helpers-huawei.c') + enums_sources, 'include_directories': huawei_inc, 'dependencies': libhelpers_dep}, + }} + + plugins_udev_rules += files('huawei/77-mm-huawei-net-port-types.rules') +endif + +# plugin: intel +if plugins_options['intel'] + sources = files( + 'intel/mm-plugin-intel.c', + ) + + if enable_mbim + sources += files('intel/mm-broadband-modem-mbim-intel.c') + endif + + common_c_args = '-DMM_MODULE_NAME="intel"' + + plugins += {'plugin-intel': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs + [xmm_inc], 'c_args': common_c_args}, + }} +endif + +# plugin: iridium +if plugins_options['iridium'] + sources = files( + 'iridium/mm-bearer-iridium.c', + 'iridium/mm-broadband-modem-iridium.c', + 'iridium/mm-plugin-iridium.c', + 'iridium/mm-sim-iridium.c', + ) + + plugins += {'plugin-iridium': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="iridium"'}, + }} +endif + +# plugin: linktop +if plugins_options['linktop'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_LINKTOP="@0@"'.format(plugins_dir / 'linktop')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="linktop"'] + + sources = files( + 'linktop/mm-plugin-linktop.c', + 'linktop/mm-broadband-modem-linktop.c', + ) + + plugins += {'plugin-linktop': { + 'plugin': true, + 'helper': {'sources': files('linktop/mm-modem-helpers-linktop.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'test': {'sources': files('linktop/tests/test-modem-helpers-linktop.c'), 'include_directories': include_directories('linktop'), 'dependencies': libhelpers_dep}, + }} + + plugins_udev_rules += files('linktop/77-mm-linktop-port-types.rules') +endif + +# plugin: longcheer (and rebranded dongles) +if plugins_options['longcheer'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_LONGCHEER="@0@"'.format(plugins_dir / 'longcheer')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + sources = files( + 'longcheer/mm-broadband-modem-longcheer.c', + 'longcheer/mm-plugin-longcheer.c', + ) + + plugins += {'plugin-longcheer': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="longcheer"']}, + }} + + plugins_udev_rules += files('longcheer/77-mm-longcheer-port-types.rules') +endif + +# plugin: ericsson mbm +if plugins_options['mbm'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_MBM="@0@"'.format(plugins_dir / 'mbm')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="ericsson-mbm"'] + + sources = files( + 'mbm/mm-broadband-bearer-mbm.c', + 'mbm/mm-broadband-modem-mbm.c', + 'mbm/mm-plugin-mbm.c', + 'mbm/mm-sim-mbm.c', + ) + + plugins += {'plugin-ericsson-mbm': { + 'plugin': true, + 'helper': {'sources': files('mbm/mm-modem-helpers-mbm.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'test': {'sources': files('mbm/tests/test-modem-helpers-mbm.c'), 'include_directories': plugins_incs + [include_directories('mbm')], 'dependencies': libhelpers_dep}, + }} + + plugins_udev_rules += files('mbm/77-mm-ericsson-mbm.rules') +endif + +# plugin: motorola +if plugins_options['motorola'] + sources = files( + 'motorola/mm-broadband-modem-motorola.c', + 'motorola/mm-plugin-motorola.c', + ) + + plugins += {'plugin-motorola': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="motorola"'}, + }} +endif + +# plugin: mtk +if plugins_options['mtk'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_MTK="@0@"'.format(plugins_dir / 'mtk')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + sources = files( + 'mtk/mm-broadband-modem-mtk.c', + 'mtk/mm-plugin-mtk.c', + ) + + plugins += {'plugin-mtk': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="motorola"']}, + }} + + plugins_udev_rules += files('mtk/77-mm-mtk-port-types.rules') +endif + +# plugin: nokia +if plugins_options['nokia'] + sources = files( + 'nokia/mm-broadband-modem-nokia.c', + 'nokia/mm-plugin-nokia.c', + 'nokia/mm-sim-nokia.c', + ) + + plugins += {'plugin-nokia': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="nokia"'}, + }} +endif + +# plugin: nokia (icera) +if plugins_options['nokia-icera'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_NOKIA_ICERA="@0@"'.format(plugins_dir / 'nokia')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + plugins += {'plugin-nokia-icera': { + 'plugin': true, + 'module': {'sources': files('nokia/mm-plugin-nokia-icera.c'), 'include_directories': plugins_incs + [icera_inc], 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="nokia-icera"']}, + }} + + plugins_udev_rules += files('nokia/77-mm-nokia-port-types.rules') +endif + +# plugin: novatel non-lte +if plugins_options['novatel'] + plugins += {'plugin-novatel': { + 'plugin': true, + 'module': {'sources': files('novatel/mm-plugin-novatel.c'), 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="novatel"'}, + }} +endif + +# plugin: novatel lte +if plugins_options['novatel-lte'] + sources = files( + 'novatel/mm-plugin-novatel-lte.c', + 'novatel/mm-broadband-modem-novatel-lte.c', + 'novatel/mm-broadband-bearer-novatel-lte.c', + 'novatel/mm-sim-novatel-lte.c', + ) + + plugins += {'plugin-novatel-lte': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="novatel-lte"'}, + }} +endif + +# plugin: option +if plugins_options['option'] + plugins += {'plugin-option': { + 'plugin': true, + 'module': {'sources': files('option/mm-plugin-option.c'), 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="option"'}, + }} +endif + +# plugin: option hso +if plugins_options['option-hso'] + sources = files( + 'option/mm-plugin-hso.c', + 'option/mm-broadband-bearer-hso.c', + 'option/mm-broadband-modem-hso.c', + ) + + plugins += {'plugin-option-hso': { + 'plugin': true, + 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="option-hso"'}, + }} +endif + +# plugin: pantech +if plugins_options['pantech'] + sources = files( + 'pantech/mm-broadband-modem-pantech.c', + 'pantech/mm-plugin-pantech.c', + 'pantech/mm-sim-pantech.c', + ) + + plugins += {'plugin-pantech': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="pantech"'}, + }} +endif + +# plugin: qcom-soc +if plugins_options['qcom-soc'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_QCOM_SOC="@0@"'.format(plugins_dir / 'qcom-soc')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + sources = files( + 'qcom-soc/mm-broadband-modem-qmi-qcom-soc.c', + 'qcom-soc/mm-plugin-qcom-soc.c', + ) + + plugins += {'plugin-qcom-soc': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="qcom-soc"']}, + }} + + plugins_udev_rules += files('qcom-soc/77-mm-qcom-soc.rules') +endif + +# plugin: quectel +if plugins_options['quectel'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_QUECTEL="@0@"'.format(plugins_dir / 'quectel')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="quectel"'] + + sources = files( + 'quectel/mm-broadband-modem-quectel.c', + 'quectel/mm-plugin-quectel.c', + 'quectel/mm-shared-quectel.c', + ) + + if enable_qmi + sources += files('quectel/mm-broadband-modem-qmi-quectel.c') + endif + + if enable_mbim + sources += files('quectel/mm-broadband-modem-mbim-quectel.c') + endif + + plugins += {'plugin-quectel': { + 'plugin': true, + 'helper': {'sources': files('quectel/mm-modem-helpers-quectel.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'test': {'sources': files('quectel/tests/test-modem-helpers-quectel.c'), 'include_directories': include_directories('quectel'), 'dependencies': libhelpers_dep}, + }} + + plugins_udev_rules += files('quectel/77-mm-quectel-port-types.rules') +endif + +# plugin: samsung +if plugins_options['samsung'] + sources = files( + 'samsung/mm-broadband-modem-samsung.c', + 'samsung/mm-plugin-samsung.c', + ) + + plugins += {'plugin-samsung': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs + [icera_inc], 'c_args': '-DMM_MODULE_NAME="samsung"'}, + }} +endif + +# plugin: sierra (legacy) +if plugins_options['sierra-legacy'] + sources = files( + 'sierra/mm-broadband-modem-sierra-icera.c', + 'sierra/mm-plugin-sierra-legacy.c', + ) + + plugins += {'plugin-sierra-legacy': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs + [icera_inc], 'c_args': '-DMM_MODULE_NAME="sierra-legacy"'}, + }} +endif + +# plugin: sierra (new QMI or MBIM modems) +if plugins_options['sierra'] + plugins += {'plugin-sierra': { + 'plugin': true, + 'module': {'sources': files('sierra/mm-plugin-sierra.c'), 'include_directories': plugins_incs + [xmm_inc], 'c_args': '-DMM_MODULE_NAME="sierra"'}, + }} + + plugins_udev_rules += files('sierra/77-mm-sierra.rules') +endif + +# plugin: simtech +if plugins_options['simtech'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_SIMTECH="@0@"'.format(plugins_dir / 'simtech')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="simtech"'] + + sources = files( + 'simtech/mm-broadband-modem-simtech.c', + 'simtech/mm-plugin-simtech.c', + 'simtech/mm-shared-simtech.c', + ) + + if enable_qmi + sources += files('simtech/mm-broadband-modem-qmi-simtech.c') + endif + + plugins += {'plugin-simtech': { + 'plugin': true, + 'helper': {'sources': files('simtech/mm-modem-helpers-simtech.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'test': {'sources': files('simtech/tests/test-modem-helpers-simtech.c'), 'include_directories': plugins_incs + [include_directories('simtech')], 'dependencies': libport_dep}, + }} + + plugins_udev_rules += files('simtech/77-mm-simtech-port-types.rules') +endif + +# plugin: telit +if plugins_options['telit'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_TELIT="@0@"'.format(plugins_dir / 'telit')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + plugins += {'plugin-telit': { + 'plugin': true, + 'module': {'sources': files('telit/mm-plugin-telit.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="telit"']}, + }} + + plugins_udev_rules += files('telit/77-mm-telit-port-types.rules') +endif + +# plugin: thuraya xt +if plugins_options['thuraya'] + common_c_args = ['-DMM_MODULE_NAME="thuraya"'] + + sources = files( + 'thuraya/mm-broadband-modem-thuraya.c', + 'thuraya/mm-plugin-thuraya.c', + ) + + plugins += {'plugin-thuraya': { + 'plugin': true, + 'helper': {'sources': files('thuraya/mm-modem-helpers-thuraya.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'test': {'sources': files('thuraya/tests/test-mm-modem-helpers-thuraya.c'), 'include_directories': include_directories('thuraya'), 'dependencies': libhelpers_dep}, + }} +endif + +# plugin: tplink +if plugins_options['tplink'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_TPLINK="@0@"'.format(plugins_dir / 'tplink')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + plugins += {'plugin-tplink': { + 'plugin': true, + 'module': {'sources': files('tplink/mm-plugin-tplink.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="tp-link"']}, + }} + + plugins_udev_rules += files('tplink/77-mm-tplink-port-types.rules') +endif + +# plugin: u-blox +if plugins_options['ublox'] + ublox_inc = include_directories('ublox') + + common_c_args = '-DMM_MODULE_NAME="u-blox"' + + headers = files('ublox/mm-modem-helpers-ublox.h') + + sources = files( + 'ublox/mm-broadband-bearer-ublox.c', + 'ublox/mm-broadband-modem-ublox.c', + 'ublox/mm-plugin-ublox.c', + 'ublox/mm-sim-ublox.c', + ) + + enums_types = 'mm-ublox-enums-types' + + sources += gnome.mkenums( + enums_types + '.c', + sources: headers, + c_template: build_aux_dir / enums_types + '.c.template', + fhead: '#include "mm-ublox-enums-types.h"', + ) + + sources += gnome.mkenums( + enums_types + '.h', + sources: headers, + h_template: build_aux_dir / enums_types + '.h.template', + fhead: '#include "mm-modem-helpers-ublox.h"\n#ifndef __MM_UBLOX_ENUMS_TYPES_H__\n#define __MM_UBLOX_ENUMS_TYPES_H__\n', + ftail: '#endif /* __MM_UBLOX_ENUMS_TYPES_H__ */\n', + ) + + plugins += {'plugin-ublox': { + 'plugin': true, + 'helper': {'sources': files('ublox/mm-modem-helpers-ublox.c'), 'include_directories': plugins_incs, 'c_args': common_c_args}, + 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs + [ublox_inc], 'c_args': common_c_args}, + 'test': {'sources': files('ublox/tests/test-modem-helpers-ublox.c'), 'include_directories': ublox_inc, 'dependencies': plugins_common_test_dep}, + }} + + plugins_udev_rules += files('ublox/77-mm-ublox-port-types.rules') +endif + +# plugin: via +if plugins_options['via'] + sources = files( + 'via/mm-broadband-modem-via.c', + 'via/mm-plugin-via.c', + ) + + plugins += {'plugin-via': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="via"'}, + }} +endif + +# plugin: wavecom (now sierra airlink) +if plugins_options['wavecom'] + sources = files( + 'wavecom/mm-broadband-modem-wavecom.c', + 'wavecom/mm-plugin-wavecom.c', + ) + + plugins += {'plugin-wavecom': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="wavecom"'}, + }} +endif + +# plugin: alcatel/TCT/JRD x220D and possibly others +if plugins_options['x22x'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_X22X="@0@"'.format(plugins_dir / 'x22x')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + sources = files( + 'x22x/mm-broadband-modem-x22x.c', + 'x22x/mm-plugin-x22x.c', + ) + + plugins += {'plugin-x22x': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="x22x"']}, + }} + + plugins_udev_rules += files('x22x/77-mm-x22x-port-types.rules') +endif + +# plugin: zte +if plugins_options['zte'] + test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_ZTE="@0@"'.format(plugins_dir / 'zte')] + plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args + + sources = files( + 'zte/mm-broadband-modem-zte.c', + 'zte/mm-broadband-modem-zte-icera.c', + 'zte/mm-common-zte.c', + 'zte/mm-plugin-zte.c', + ) + + plugins += {'plugin-zte': { + 'plugin': true, + 'module': {'sources': sources, 'include_directories': plugins_incs + [icera_inc], 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="zte"']}, + }} + + plugins_udev_rules += files('zte/77-mm-zte-port-types.rules') +endif + +foreach plugin_name, plugin_data: plugins + libpluginhelpers = [] + if plugin_data.has_key('helper') + libpluginhelpers = static_library( + 'helpers-' + plugin_name, + dependencies: plugins_deps, + kwargs: plugin_data['helper'], + ) + endif + + module_args = plugin_data['module'] + if plugin_data['plugin'] + module_args += { + 'link_args': ldflags, + 'link_depends': symbol_map, + } + endif + + shared_module( + 'mm-' + plugin_name, + dependencies: plugins_deps, + link_with: libpluginhelpers, + kwargs: module_args, + install: true, + install_dir: mm_pkglibdir, + ) + + if enable_tests + if plugin_data.has_key('test') + test_unit = 'test-' + plugin_name + + exe = executable( + test_unit, + link_with: libpluginhelpers, + kwargs: plugin_data['test'], + ) + + test(test_unit, exe) + endif + endif +endforeach + +install_data( + plugins_data, + install_dir: mm_pkgdatadir, +) + +install_data( + plugins_udev_rules, + install_dir: udev_rulesdir, +) + +# udev-rules and keyfiles tests +test_units = { + 'udev-rules': {'include_directories': top_inc, 'dependencies': libkerneldevice_dep, 'c_args': plugins_test_udev_rules_dir_c_args}, + 'keyfiles': {'include_directories': [top_inc, src_inc], 'dependencies': libmm_glib_dep, 'c_args': plugins_test_keyfile_c_args}, +} + +foreach name, data: test_units + test_name = 'test-' + name + + exe = executable( + test_name, + sources: 'tests/@0@.c'.format(test_name), + kwargs: data, + ) + + test(test_name, exe) +endforeach diff --git a/src/plugins/motorola/mm-broadband-modem-motorola.c b/src/plugins/motorola/mm-broadband-modem-motorola.c new file mode 100644 index 00000000..8ebd2114 --- /dev/null +++ b/src/plugins/motorola/mm-broadband-modem-motorola.c @@ -0,0 +1,92 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2011 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-broadband-modem-motorola.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMotorola, mm_broadband_modem_motorola, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)); + +/*****************************************************************************/ + +MMBroadbandModemMotorola * +mm_broadband_modem_motorola_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MOTOROLA, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_motorola_init (MMBroadbandModemMotorola *self) +{ +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + /* Loading IMEI not supported */ + iface->load_imei = NULL; + iface->load_imei_finish = NULL; +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + /* Loading IMEI with +CGSN is not supported, just assume we cannot load + * equipment ID */ + iface->load_equipment_identifier = NULL; + iface->load_equipment_identifier_finish = NULL; + + /* These devices just don't implement AT+CFUN */ + iface->load_power_state = NULL; + iface->load_power_state_finish = NULL; + iface->modem_power_up = NULL; + iface->modem_power_up_finish = NULL; + iface->modem_power_down = NULL; + iface->modem_power_down_finish = NULL; +} + +static void +mm_broadband_modem_motorola_class_init (MMBroadbandModemMotorolaClass *klass) +{ +} diff --git a/src/plugins/motorola/mm-broadband-modem-motorola.h b/src/plugins/motorola/mm-broadband-modem-motorola.h new file mode 100644 index 00000000..f57e5b3d --- /dev/null +++ b/src/plugins/motorola/mm-broadband-modem-motorola.h @@ -0,0 +1,49 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_MOTOROLA_H +#define MM_BROADBAND_MODEM_MOTOROLA_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_MOTOROLA (mm_broadband_modem_motorola_get_type ()) +#define MM_BROADBAND_MODEM_MOTOROLA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MOTOROLA, MMBroadbandModemMotorola)) +#define MM_BROADBAND_MODEM_MOTOROLA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MOTOROLA, MMBroadbandModemMotorolaClass)) +#define MM_IS_BROADBAND_MODEM_MOTOROLA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MOTOROLA)) +#define MM_IS_BROADBAND_MODEM_MOTOROLA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MOTOROLA)) +#define MM_BROADBAND_MODEM_MOTOROLA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MOTOROLA, MMBroadbandModemMotorolaClass)) + +typedef struct _MMBroadbandModemMotorola MMBroadbandModemMotorola; +typedef struct _MMBroadbandModemMotorolaClass MMBroadbandModemMotorolaClass; + +struct _MMBroadbandModemMotorola { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemMotorolaClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_motorola_get_type (void); + +MMBroadbandModemMotorola *mm_broadband_modem_motorola_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_MOTOROLA_H */ diff --git a/src/plugins/motorola/mm-plugin-motorola.c b/src/plugins/motorola/mm-plugin-motorola.c new file mode 100644 index 00000000..fbe9b2fa --- /dev/null +++ b/src/plugins/motorola/mm-plugin-motorola.c @@ -0,0 +1,84 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-private-boxed-types.h" +#include "mm-plugin-motorola.h" +#include "mm-broadband-modem-motorola.h" + +G_DEFINE_TYPE (MMPluginMotorola, mm_plugin_motorola, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_motorola_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + static const mm_uint16_pair product_ids[] = { + { 0x22b8, 0x3802 }, /* C330/C350L/C450/EZX GSM Phone */ + { 0x22b8, 0x4902 }, /* Triplet GSM Phone */ + { 0, 0 } + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_MOTOROLA, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_PRODUCT_IDS, product_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + NULL)); +} + +static void +mm_plugin_motorola_init (MMPluginMotorola *self) +{ +} + +static void +mm_plugin_motorola_class_init (MMPluginMotorolaClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/motorola/mm-plugin-motorola.h b/src/plugins/motorola/mm-plugin-motorola.h new file mode 100644 index 00000000..6812ba36 --- /dev/null +++ b/src/plugins/motorola/mm-plugin-motorola.h @@ -0,0 +1,42 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_MOTOROLA_H +#define MM_PLUGIN_MOTOROLA_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_MOTOROLA (mm_plugin_motorola_get_type ()) +#define MM_PLUGIN_MOTOROLA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_MOTOROLA, MMPluginMotorola)) +#define MM_PLUGIN_MOTOROLA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_MOTOROLA, MMPluginMotorolaClass)) +#define MM_IS_PLUGIN_MOTOROLA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_MOTOROLA)) +#define MM_IS_PLUGIN_MOTOROLA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_MOTOROLA)) +#define MM_PLUGIN_MOTOROLA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_MOTOROLA, MMPluginMotorolaClass)) + +typedef struct { + MMPlugin parent; +} MMPluginMotorola; + +typedef struct { + MMPluginClass parent; +} MMPluginMotorolaClass; + +GType mm_plugin_motorola_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_MOTOROLA_H */ diff --git a/src/plugins/mtk/77-mm-mtk-port-types.rules b/src/plugins/mtk/77-mm-mtk-port-types.rules new file mode 100644 index 00000000..96939fd4 --- /dev/null +++ b/src/plugins/mtk/77-mm-mtk-port-types.rules @@ -0,0 +1,53 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_mtk_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0e8d", GOTO="mm_mtk_port_types_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2001", GOTO="mm_dlink_port_types_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="07d1", GOTO="mm_dlink_port_types_vendorcheck" +GOTO="mm_mtk_port_types_end" + +# MediaTek devices --------------------------- + +LABEL="mm_mtk_port_types_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a1", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a1", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a1", ENV{ID_MM_MTK_TAGGED}="1" + +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a2", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a2", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a2", ENV{ID_MM_MTK_TAGGED}="1" + +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a4", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a4", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a4", ENV{ID_MM_MTK_TAGGED}="1" + +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a5", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a5", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a5", ENV{ID_MM_MTK_TAGGED}="1" + +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a7", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a7", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a7", ENV{ID_MM_MTK_TAGGED}="1" + +GOTO="mm_mtk_port_types_end" + +# D-Link devices --------------------------- + +LABEL="mm_dlink_port_types_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# D-Link DWM-156 A3 +ATTRS{idVendor}=="07d1", ATTRS{idProduct}=="7e11", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="07d1", ATTRS{idProduct}=="7e11", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="07d1", ATTRS{idProduct}=="7e11", ENV{ID_MM_MTK_TAGGED}="1" + +# D-Link DWM-156 A5 (and later?) +ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7d00", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7d00", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7d00", ENV{ID_MM_MTK_TAGGED}="1" + +GOTO="mm_mtk_port_types_end" + +LABEL="mm_mtk_port_types_end" diff --git a/src/plugins/mtk/mm-broadband-modem-mtk.c b/src/plugins/mtk/mm-broadband-modem-mtk.c new file mode 100644 index 00000000..869784f3 --- /dev/null +++ b/src/plugins/mtk/mm-broadband-modem-mtk.c @@ -0,0 +1,934 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-broadband-modem-mtk.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMtk, mm_broadband_modem_mtk, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)); + +struct _MMBroadbandModemMtkPrivate { + /* Signal quality regex */ + GRegex *ecsqg_regex; + GRegex *ecsqu_regex; + GRegex *ecsqeg_regex; + GRegex *ecsqeu_regex; + GRegex *ecsqel_regex; +}; + +/*****************************************************************************/ +/* Unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_unlock_retries_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GRegex) r = NULL; + const gchar *response; + GError *error = NULL; + GError *match_error = NULL; + gint pin1; + gint puk1; + gint pin2; + gint puk2; + MMUnlockRetries *retries; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + r = g_regex_new ( + "\\+EPINC:\\s*([0-9]+),\\s*([0-9]+),\\s*([0-9]+),\\s*([0-9]+)", + 0, + 0, + NULL); + + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &match_error)){ + if (match_error) + g_task_return_error (task, match_error); + else + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to match EPINC response: %s", response); + g_task_return_error (task, error); + } else if (!mm_get_int_from_match_info (match_info, 1, &pin1) || + !mm_get_int_from_match_info (match_info, 2, &pin2) || + !mm_get_int_from_match_info (match_info, 3, &puk1) || + !mm_get_int_from_match_info (match_info, 4, &puk2)) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse the EPINC response: '%s'", + response); + } else { + retries = mm_unlock_retries_new (); + + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2); + + g_task_return_pointer (task, retries, g_object_unref); + } + g_object_unref (task); +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+EPINC?", + 3, + FALSE, + (GAsyncReadyCallback)load_unlock_retries_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +static gboolean +modem_after_sim_unlock_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +after_sim_unlock_wait_cb (GTask *task) +{ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +modem_after_sim_unlock (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* For device, 3 second is OK for SIM get ready */ + g_timeout_add_seconds (3, (GSourceFunc)after_sim_unlock_wait_cb, task); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static void +get_supported_modes_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) + +{ + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GRegex) r = NULL; + const gchar *response; + GError *error = NULL; + MMModemModeCombination mode; + GArray *combinations; + GError *match_error = NULL; + gint device_type; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + r = g_regex_new ("\\+EGMR:\\s*\"MT([0-9]+)", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &match_error)) { + if (match_error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to match EGMR response: %s", response); + g_object_unref (task); + return; + } + + if (!mm_get_int_from_match_info (match_info, 1, &device_type)) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to parse the allowed mode response: '%s'", + response); + g_object_unref (task); + return; + } + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, + FALSE, + sizeof (MMModemModeCombination), + 8); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G, no prefer*/ + mode.allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G, 3G prefer*/ + mode.allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + + if (device_type == 6290) { + /* 4G only */ + mode.allowed = MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 4G, no prefer */ + mode.allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G and 4G, no prefer */ + mode.allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G, 3G and 4G, no prefer */ + mode.allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + + /********************************************************************* + * No need to filter out any unsupported modes for MTK device. For + * +GCAP, +WS64 not support completely, generic filter will filter + * out 4G modes. + */ + g_task_return_pointer (task, combinations, (GDestroyNotify)g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+EGMR=0,0", + 3, + FALSE, + (GAsyncReadyCallback)get_supported_modes_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GRegex) r = NULL; + const gchar *response; + gint erat_mode = -1; + gint erat_pref = -1; + GError *match_error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + r = g_regex_new ( + "\\+ERAT:\\s*[0-9]+,\\s*[0-9]+,\\s*([0-9]+),\\s*([0-9]+)", + 0, + 0, + error); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &match_error)) { + if (match_error) + g_propagate_error (error, match_error); + else + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse +ERAT response: '%s'", + response); + return FALSE; + } + + if (!mm_get_int_from_match_info (match_info, 1, &erat_mode) || + !mm_get_int_from_match_info (match_info, 2, &erat_pref)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to parse the ERAT response: m=%d p=%d", + erat_mode, erat_pref); + return FALSE; + } + + /* Correctly parsed! */ + switch (erat_mode) { + case 0: + *allowed = MM_MODEM_MODE_2G; + break; + case 1: + *allowed = MM_MODEM_MODE_3G; + break; + case 2: + *allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G; + break; + case 3: + *allowed = MM_MODEM_MODE_4G; + break; + case 4: + *allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G; + break; + case 5: + *allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G; + break; + case 6: + *allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G; + break; + default: + mm_obj_dbg (self, "unsupported allowed mode reported in +ERAT: %d", erat_mode); + return FALSE; + } + + switch (erat_pref) { + case 0: + *preferred = MM_MODEM_MODE_NONE; + break; + case 1: + *preferred = MM_MODEM_MODE_2G; + break; + case 2: + *preferred = MM_MODEM_MODE_3G; + break; + case 3: + *preferred = MM_MODEM_MODE_4G; + break; + default: + mm_obj_dbg (self, "unsupported preferred mode %d", erat_pref); + return FALSE; + } + + return TRUE; +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+ERAT?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +allowed_mode_update_ready (MMBroadbandModemMtk *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (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); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *command; + gint erat_mode = -1; + gint erat_pref = -1; + + task = g_task_new (self, NULL, callback, user_data); + + if (allowed == MM_MODEM_MODE_2G) { + erat_mode = 0; + erat_pref = 0; + } else if (allowed == MM_MODEM_MODE_3G) { + erat_mode = 1; + erat_pref = 0; + } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { + erat_mode = 2; + if (preferred == MM_MODEM_MODE_3G) + erat_pref = 2; + else if (preferred == MM_MODEM_MODE_NONE) + erat_pref = 0; + /* 2G prefer not supported */ + } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G) && + preferred == MM_MODEM_MODE_NONE) { + erat_mode = 6; + erat_pref = 0; + } else if ((allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G)) && + preferred == MM_MODEM_MODE_NONE) { + erat_mode = 4; + erat_pref = 0; + } else if ((allowed == (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G)) && + preferred == MM_MODEM_MODE_NONE) { + erat_mode = 5; + erat_pref = 0; + } else if (allowed == MM_MODEM_MODE_4G) { + erat_mode = 3; + erat_pref = 0; + } + + if (erat_mode < 0 || erat_pref < 0) { + 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_object_unref (task); + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("AT+ERAT=%d,%d", erat_mode, erat_pref); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 30, + FALSE, + (GAsyncReadyCallback)allowed_mode_update_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static void +mtk_80_signal_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemMtk *self) +{ + guint quality = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &quality)) + return; + + if (quality == 99) + quality = 0; + else + quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31; + + mm_obj_dbg (self, "6280 signal quality URC received: %u", quality); + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); +} + +static void +mtk_90_2g_signal_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemMtk *self) +{ + guint quality = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &quality)) + return; + + if (quality == 99) + quality = 0; + else + quality = MM_CLAMP_HIGH (quality, 63) * 100 / 63; + + mm_obj_dbg (self, "2G signal quality URC received: %u", quality); + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); +} + +static void +mtk_90_3g_signal_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemMtk *self) +{ + guint quality = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &quality)) + return; + + quality = MM_CLAMP_HIGH (quality, 96) * 100 / 96; + + mm_obj_dbg (self, "3G signal quality URC received: %u", quality); + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); +} + +static void +mtk_90_4g_signal_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemMtk *self) +{ + guint quality = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &quality)) + return; + + quality = MM_CLAMP_HIGH (quality, 97) * 100 / 97; + + mm_obj_dbg (self, "4G signal quality URC received: %u", quality); + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); +} + +static void +set_unsolicited_events_handlers (MMBroadbandModemMtk *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable/disable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++){ + if(!ports[i]) + continue; + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->ecsqg_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)mtk_80_signal_changed : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->ecsqu_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)mtk_80_signal_changed : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->ecsqeg_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)mtk_90_2g_signal_changed:NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->ecsqeu_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)mtk_90_3g_signal_changed:NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->ecsqel_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)mtk_90_4g_signal_changed:NULL, + enable ? self : NULL, + NULL); + } +} + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MTK (self), + TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static void +parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own cleanup first */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MTK (self), FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static const MMBaseModemAtCommand unsolicited_enable_sequence[] = { + /* enable signal URC */ + { "+ECSQ=2", 5, FALSE, NULL }, + { NULL } +}; + +static const MMBaseModemAtCommand unsolicited_disable_sequence[] = { + /* disable signal URC */ + { "+ECSQ=0" , 5, FALSE, NULL }, + { NULL } +}; + +static void +own_enable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + } + + /* Our own enable now */ + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_enable_sequence, + NULL,NULL,NULL, + (GAsyncReadyCallback)own_enable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's enable */ + iface_modem_3gpp_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static gboolean +modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +own_disable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Next, chain up parent's disable */ + iface_modem_3gpp_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own disable first */ + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_disable_sequence, + NULL, NULL, NULL, + (GAsyncReadyCallback)own_disable_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static gboolean +modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *self) +{ + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mtk_parent_class)->setup_ports (self); + + /* Now reset the unsolicited messages we'll handle when enabled */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MTK (self), FALSE); +} + +/*****************************************************************************/ +MMBroadbandModemMtk * +mm_broadband_modem_mtk_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MTK, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_mtk_init (MMBroadbandModemMtk *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_MODEM_MTK, + MMBroadbandModemMtkPrivate); + self->priv->ecsqg_regex = g_regex_new ( + "\\r\\n\\+ECSQ:\\s*([0-9]*),\\s*[0-9]*,\\s*-[0-9]*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ecsqu_regex = g_regex_new ( + "\\r\\n\\+ECSQ:\\s*([0-9]*),\\s*[0-9]*,\\s*-[0-9]*,\\s*-[0-9]*,\\s*-[0-9]*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ecsqeg_regex = g_regex_new ( + "\\r\\n\\+ECSQ:\\s*([0-9]*),\\s*[0-9]*,\\s*-[0-9]*,\\s*1,\\s*1,\\s*1,\\s*1,\\s*[0-9]*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ecsqeu_regex = g_regex_new ( + "\\r\\n\\+ECSQ:\\s*([0-9]*),\\s*[0-9]*,\\s*1,\\s*-[0-9]*,\\s*-[0-9]*,\\s*1,\\s*1,\\s*[0-9]*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ecsqel_regex = g_regex_new ( + "\\r\\n\\+ECSQ:\\s*[0-9]*,\\s*([0-9]*),\\s*1,\\s*1,\\s*1,\\s*-[0-9]*,\\s*-[0-9]*,\\s*[0-9]*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemMtk *self = MM_BROADBAND_MODEM_MTK (object); + + g_regex_unref (self->priv->ecsqg_regex); + g_regex_unref (self->priv->ecsqu_regex); + g_regex_unref (self->priv->ecsqeg_regex); + g_regex_unref (self->priv->ecsqeu_regex); + g_regex_unref (self->priv->ecsqel_regex); + + G_OBJECT_CLASS (mm_broadband_modem_mtk_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->modem_after_sim_unlock = modem_after_sim_unlock; + iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish; +} + +static void +mm_broadband_modem_mtk_class_init (MMBroadbandModemMtkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemMtkPrivate)); + + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/mtk/mm-broadband-modem-mtk.h b/src/plugins/mtk/mm-broadband-modem-mtk.h new file mode 100644 index 00000000..bbe72cc2 --- /dev/null +++ b/src/plugins/mtk/mm-broadband-modem-mtk.h @@ -0,0 +1,51 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2011 Red Hat, Inc. + * Copyright (C) 2011 Google Inc. + */ + +#ifndef MM_BROADBAND_MODEM_MTK_H +#define MM_BROADBAND_MODEM_MTK_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_MTK (mm_broadband_modem_mtk_get_type ()) +#define MM_BROADBAND_MODEM_MTK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MTK, MMBroadbandModemMtk)) +#define MM_BROADBAND_MODEM_MTK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MTK, MMBroadbandModemMtkClass)) +#define MM_IS_BROADBAND_MODEM_MTK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MTK)) +#define MM_IS_BROADBAND_MODEM_MTK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MTK)) +#define MM_BROADBAND_MODEM_MTK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MTK, MMBroadbandModemMtkClass)) + +typedef struct _MMBroadbandModemMtk MMBroadbandModemMtk; +typedef struct _MMBroadbandModemMtkClass MMBroadbandModemMtkClass; +typedef struct _MMBroadbandModemMtkPrivate MMBroadbandModemMtkPrivate; + +struct _MMBroadbandModemMtk { + MMBroadbandModem parent; + MMBroadbandModemMtkPrivate *priv; +}; + +struct _MMBroadbandModemMtkClass { + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_mtk_get_type (void); + +MMBroadbandModemMtk *mm_broadband_modem_mtk_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_MTK_H */ diff --git a/src/plugins/mtk/mm-plugin-mtk.c b/src/plugins/mtk/mm-plugin-mtk.c new file mode 100644 index 00000000..957e38a3 --- /dev/null +++ b/src/plugins/mtk/mm-plugin-mtk.c @@ -0,0 +1,82 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-mtk.h" +#include "mm-broadband-modem-mtk.h" + +G_DEFINE_TYPE (MMPluginMtk, mm_plugin_mtk, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +/* MTK done */ +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_mtk_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + static const gchar *udev_tags[]={ + "ID_MM_MTK_TAGGED", + NULL}; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_MTK, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags, + MM_PLUGIN_ALLOWED_AT, TRUE, + NULL)); +} + +static void +mm_plugin_mtk_init (MMPluginMtk *self) +{ +} + +static void +mm_plugin_mtk_class_init (MMPluginMtkClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/mtk/mm-plugin-mtk.h b/src/plugins/mtk/mm-plugin-mtk.h new file mode 100644 index 00000000..cc7b0d19 --- /dev/null +++ b/src/plugins/mtk/mm-plugin-mtk.h @@ -0,0 +1,42 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_MTK_H +#define MM_PLUGIN_MTK_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_MTK (mm_plugin_mtk_get_type ()) +#define MM_PLUGIN_MTK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_MTK, MMPluginMtk)) +#define MM_PLUGIN_MTK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_MTK, MMPluginMtkClass)) +#define MM_IS_PLUGIN_MTK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_MTK)) +#define MM_IS_PLUGIN_MTK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_MTK)) +#define MM_PLUGIN_MTK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_MTK, MMPluginMtkClass)) + +typedef struct { + MMPlugin parent; +} MMPluginMtk; + +typedef struct { + MMPluginClass parent; +} MMPluginMtkClass; + +GType mm_plugin_mtk_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_MTK_H */ diff --git a/src/plugins/nokia/77-mm-nokia-port-types.rules b/src/plugins/nokia/77-mm-nokia-port-types.rules new file mode 100644 index 00000000..daa8bd4a --- /dev/null +++ b/src/plugins/nokia/77-mm-nokia-port-types.rules @@ -0,0 +1,38 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_nokia_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0421", GOTO="mm_nokia_port_types" +GOTO="mm_nokia_port_types_end" + +LABEL="mm_nokia_port_types" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# For Nokia Internet Sticks (CS-xx) the modem/PPP port appears to always be USB interface 1 + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="060D", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0611", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="061A", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="061B", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="061F", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0619", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0620", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0623", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0624", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0625", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="062A", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="062E", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="062F", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +LABEL="mm_nokia_port_types_end" diff --git a/src/plugins/nokia/mm-broadband-modem-nokia.c b/src/plugins/nokia/mm-broadband-modem-nokia.c new file mode 100644 index 00000000..fd608868 --- /dev/null +++ b/src/plugins/nokia/mm-broadband-modem-nokia.c @@ -0,0 +1,399 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2011 Red Hat, Inc. + * Copyright (C) 2011 Google Inc. + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-serial-parsers.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-messaging.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-modem-helpers.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-nokia.h" +#include "mm-sim-nokia.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_messaging_init (MMIfaceModemMessaging *iface); + +static MMIfaceModem *iface_modem_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemNokia, mm_broadband_modem_nokia, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)); + +/*****************************************************************************/ +/* Create SIM (Modem interface) */ + +static MMBaseSim * +create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_nokia_new_finish (res, error); +} + +static void +create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* New Nokia SIM */ + mm_sim_nokia_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +typedef struct { + MMModemAccessTechnology act; + guint mask; +} AccessTechInfo; + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + *access_technologies = (MMModemAccessTechnology)value; + *mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK; + return TRUE; +} + +static void +parent_load_access_technologies_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + guint mask = 0; + GError *error = NULL; + + if (!iface_modem_parent->load_access_technologies_finish (self, res, &act, &mask, &error)) + g_task_return_error (task, error); + else + g_task_return_int (task, act); + + g_object_unref (task); +} + +static void +access_tech_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + const gchar *response, *p; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL); + if (!response) { + /* Chain up to parent */ + iface_modem_parent->load_access_technologies ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_access_technologies_ready, + task); + return; + } + + p = mm_strip_tag (response, "*CNTI:"); + p = strchr (p, ','); + if (p) + act = mm_string_to_access_tech (p + 1); + + if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN) + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse access technologies result: '%s'", + response); + else + g_task_return_int (task, act); + + g_object_unref (task); +} + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*CNTI=0", + 3, + FALSE, + (GAsyncReadyCallback)access_tech_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Loading supported charsets (Modem interface) */ + +static MMModemCharset +load_supported_charsets_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + const gchar *response; + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return MM_MODEM_CHARSET_UNKNOWN; + + if (!mm_3gpp_parse_cscs_test_response (response, &charsets)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to parse the supported character sets response"); + return MM_MODEM_CHARSET_UNKNOWN; + } + + return charsets; +} + +static void +load_supported_charsets (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CSCS=?", + 20, + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Initializing the modem (during first enabling) */ + +typedef struct { + guint retries; +} EnablingModemInitContext; + +static gboolean +enabling_modem_init_finish (MMBroadbandModem *self, + GAsyncResult *res, + GError **error) + +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void retry_atz (GTask *task); + +static void +atz_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + EnablingModemInitContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + /* One retry less */ + ctx->retries--; + + if (!mm_base_modem_at_command_full_finish (self, res, &error)) { + /* Consumed all retries... */ + if (ctx->retries == 0) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Retry... */ + g_error_free (error); + retry_atz (task); + return; + } + + /* Good! */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +retry_atz (GTask *task) +{ + MMBaseModem *self; + + self = g_task_get_source_object (task); + + mm_base_modem_at_command_full (self, + mm_base_modem_peek_port_primary (self), + "Z", + 6, + FALSE, + FALSE, + NULL, /* cancellable */ + (GAsyncReadyCallback)atz_ready, + task); +} + +static void +enabling_modem_init (MMBroadbandModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnablingModemInitContext *ctx; + GTask *task; + + ctx = g_new (EnablingModemInitContext, 1); + + /* Send the init command twice; some devices (Nokia N900) appear to take a + * few commands before responding correctly. Instead of penalizing them for + * being stupid the first time by failing to enable the device, just + * try again. */ + ctx->retries = 2; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + retry_atz (task); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static const gchar *primary_init_sequence[] = { + /* When initializing a Nokia port, first enable the echo, + * and then disable it, so that we get it properly disabled. */ + "E1 E0", + /* The N900 ignores the E0 when it's on the same line as the E1, so try again */ + "E0", + /* Get word responses */ + "V1", + /* Extended numeric codes */ + "+CMEE=1", + /* Report all call status */ + "X4", + /* Assert DCD when carrier detected */ + "&C1", + NULL +}; + +static void +setup_ports (MMBroadbandModem *self) +{ + MMPortSerialAt *primary; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_nokia_parent_class)->setup_ports (self); + + primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + + g_object_set (primary, + MM_PORT_SERIAL_AT_INIT_SEQUENCE, primary_init_sequence, + NULL); +} + +/*****************************************************************************/ + +MMBroadbandModemNokia * +mm_broadband_modem_nokia_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_NOKIA, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_nokia_init (MMBroadbandModemNokia *self) +{ +} + +static void +iface_modem_messaging_init (MMIfaceModemMessaging *iface) +{ + /* Don't even try to check messaging support */ + iface->check_support = NULL; + iface->check_support_finish = NULL; +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + /* Create Nokia-specific SIM*/ + iface->create_sim = create_sim; + iface->create_sim_finish = create_sim_finish; + + /* Nokia handsets (at least N85) do not support "power on"; they do + * support "power off" but you proabably do not want to turn off the + * power on your telephone if something went wrong with connecting + * process. So, disabling both these operations. The Nokia GSM/UMTS command + * reference v1.2 also states that only CFUN=0 (turn off but still charge) + * and CFUN=1 (full functionality) are supported, and since the phone has + * to be in CFUN=1 before we'll be able to talk to it in the first place, + * we shouldn't bother with CFUN at all. + */ + iface->load_power_state = NULL; + iface->load_power_state_finish = NULL; + iface->modem_power_up = NULL; + iface->modem_power_up_finish = NULL; + iface->modem_power_down = NULL; + iface->modem_power_down_finish = NULL; + iface->load_supported_charsets = load_supported_charsets; + iface->load_supported_charsets_finish = load_supported_charsets_finish; + + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; +} + +static void +mm_broadband_modem_nokia_class_init (MMBroadbandModemNokiaClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = setup_ports; + broadband_modem_class->enabling_modem_init = enabling_modem_init; + broadband_modem_class->enabling_modem_init_finish = enabling_modem_init_finish; +} diff --git a/src/plugins/nokia/mm-broadband-modem-nokia.h b/src/plugins/nokia/mm-broadband-modem-nokia.h new file mode 100644 index 00000000..d00a5bcb --- /dev/null +++ b/src/plugins/nokia/mm-broadband-modem-nokia.h @@ -0,0 +1,49 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2011 Red Hat, Inc. + * Copyright (C) 2011 Google Inc. + */ + +#ifndef MM_BROADBAND_MODEM_NOKIA_H +#define MM_BROADBAND_MODEM_NOKIA_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_NOKIA (mm_broadband_modem_nokia_get_type ()) +#define MM_BROADBAND_MODEM_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_NOKIA, MMBroadbandModemNokia)) +#define MM_BROADBAND_MODEM_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_NOKIA, MMBroadbandModemNokiaClass)) +#define MM_IS_BROADBAND_MODEM_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_NOKIA)) +#define MM_IS_BROADBAND_MODEM_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_NOKIA)) +#define MM_BROADBAND_MODEM_NOKIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_NOKIA, MMBroadbandModemNokiaClass)) + +typedef struct _MMBroadbandModemNokia MMBroadbandModemNokia; +typedef struct _MMBroadbandModemNokiaClass MMBroadbandModemNokiaClass; + +struct _MMBroadbandModemNokia { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemNokiaClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_nokia_get_type (void); + +MMBroadbandModemNokia *mm_broadband_modem_nokia_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_NOKIA_H */ diff --git a/src/plugins/nokia/mm-plugin-nokia-icera.c b/src/plugins/nokia/mm-plugin-nokia-icera.c new file mode 100644 index 00000000..78c8fd4c --- /dev/null +++ b/src/plugins/nokia/mm-plugin-nokia-icera.c @@ -0,0 +1,90 @@ +/* -*- 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) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-nokia-icera.h" +#include "mm-broadband-modem-icera.h" + +G_DEFINE_TYPE (MMPluginNokiaIcera, mm_plugin_nokia_icera, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ +/* Custom commands for AT probing */ + +static const MMPortProbeAtCommand custom_at_probe[] = { + { "ATE1 E0", 3, mm_port_probe_response_processor_is_at }, + { "ATE1 E0", 3, mm_port_probe_response_processor_is_at }, + { "ATE1 E0", 3, mm_port_probe_response_processor_is_at }, + { NULL } +}; + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_icera_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", NULL }; + static const guint16 vendor_ids[] = { 0x0421, 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_NOKIA_ICERA, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_CUSTOM_AT_PROBE, custom_at_probe, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_ICERA, TRUE, /* Only Nokia/Icera modems */ + NULL)); +} + +static void +mm_plugin_nokia_icera_init (MMPluginNokiaIcera *self) +{ +} + +static void +mm_plugin_nokia_icera_class_init (MMPluginNokiaIceraClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/nokia/mm-plugin-nokia-icera.h b/src/plugins/nokia/mm-plugin-nokia-icera.h new file mode 100644 index 00000000..137692fb --- /dev/null +++ b/src/plugins/nokia/mm-plugin-nokia-icera.h @@ -0,0 +1,41 @@ +/* -*- 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) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_NOKIA_ICERA_H +#define MM_PLUGIN_NOKIA_ICERA_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_NOKIA_ICERA (mm_plugin_nokia_icera_get_type ()) +#define MM_PLUGIN_NOKIA_ICERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_NOKIA_ICERA, MMPluginNokiaIcera)) +#define MM_PLUGIN_NOKIA_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_NOKIA_ICERA, MMPluginNokiaIceraClass)) +#define MM_IS_PLUGIN_NOKIA_ICERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_NOKIA_ICERA)) +#define MM_IS_PLUGIN_NOKIA_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_NOKIA_ICERA)) +#define MM_PLUGIN_NOKIA_ICERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_NOKIA_ICERA, MMPluginNokiaIceraClass)) + +typedef struct { + MMPlugin parent; +} MMPluginNokiaIcera; + +typedef struct { + MMPluginClass parent; +} MMPluginNokiaIceraClass; + +GType mm_plugin_nokia_icera_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_NOKIA_ICERA_H */ diff --git a/src/plugins/nokia/mm-plugin-nokia.c b/src/plugins/nokia/mm-plugin-nokia.c new file mode 100644 index 00000000..b2700b70 --- /dev/null +++ b/src/plugins/nokia/mm-plugin-nokia.c @@ -0,0 +1,93 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2011 Red Hat, Inc. + * Copyright (C) 2011 - 2012 Google, Inc. + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-nokia.h" +#include "mm-broadband-modem-nokia.h" + +G_DEFINE_TYPE (MMPluginNokia, mm_plugin_nokia, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ +/* Custom commands for AT probing */ + +static const MMPortProbeAtCommand custom_at_probe[] = { + { "ATE1 E0", 3, mm_port_probe_response_processor_is_at }, + { "ATE1 E0", 3, mm_port_probe_response_processor_is_at }, + { "ATE1 E0", 3, mm_port_probe_response_processor_is_at }, + { NULL } +}; + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_nokia_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + static const guint16 vendor_ids[] = { 0x0421, 0 }; + static const gchar *vendor_strings[] = { "nokia", NULL }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_NOKIA, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings, + MM_PLUGIN_CUSTOM_AT_PROBE, custom_at_probe, + MM_PLUGIN_ALLOWED_SINGLE_AT, TRUE, /* only 1 AT port expected! */ + MM_PLUGIN_FORBIDDEN_ICERA, TRUE, /* No Nokia/Icera modems */ + NULL)); +} + +static void +mm_plugin_nokia_init (MMPluginNokia *self) +{ +} + +static void +mm_plugin_nokia_class_init (MMPluginNokiaClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/nokia/mm-plugin-nokia.h b/src/plugins/nokia/mm-plugin-nokia.h new file mode 100644 index 00000000..e2f3589b --- /dev/null +++ b/src/plugins/nokia/mm-plugin-nokia.h @@ -0,0 +1,41 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef MM_PLUGIN_NOKIA_H +#define MM_PLUGIN_NOKIA_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_NOKIA (mm_plugin_nokia_get_type ()) +#define MM_PLUGIN_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_NOKIA, MMPluginNokia)) +#define MM_PLUGIN_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_NOKIA, MMPluginNokiaClass)) +#define MM_IS_PLUGIN_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_NOKIA)) +#define MM_IS_PLUGIN_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_NOKIA)) +#define MM_PLUGIN_NOKIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_NOKIA, MMPluginNokiaClass)) + +typedef struct { + MMPlugin parent; +} MMPluginNokia; + +typedef struct { + MMPluginClass parent; +} MMPluginNokiaClass; + +GType mm_plugin_nokia_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_NOKIA_H */ diff --git a/src/plugins/nokia/mm-sim-nokia.c b/src/plugins/nokia/mm-sim-nokia.c new file mode 100644 index 00000000..a0d7c81a --- /dev/null +++ b/src/plugins/nokia/mm-sim-nokia.c @@ -0,0 +1,86 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-sim-nokia.h" + +G_DEFINE_TYPE (MMSimNokia, mm_sim_nokia, MM_TYPE_BASE_SIM) + +/*****************************************************************************/ + +MMBaseSim * +mm_sim_nokia_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *sim; + + source = g_async_result_get_source_object (res); + sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!sim) + return NULL; + + /* Only export valid SIMs */ + mm_base_sim_export (MM_BASE_SIM (sim)); + + return MM_BASE_SIM (sim); +} + +void +mm_sim_nokia_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (MM_TYPE_SIM_NOKIA, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_SIM_MODEM, modem, + "active", TRUE, /* by default always active */ + NULL); +} + +static void +mm_sim_nokia_init (MMSimNokia *self) +{ +} + +static void +mm_sim_nokia_class_init (MMSimNokiaClass *klass) +{ + MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass); + + /* Skip querying most SIM card info, +CRSM not supported by Nokia modems */ + base_sim_class->load_sim_identifier = NULL; + base_sim_class->load_sim_identifier_finish = NULL; + base_sim_class->load_operator_identifier = NULL; + base_sim_class->load_operator_identifier_finish = NULL; + base_sim_class->load_operator_name = NULL; + base_sim_class->load_operator_name_finish = NULL; +} diff --git a/src/plugins/nokia/mm-sim-nokia.h b/src/plugins/nokia/mm-sim-nokia.h new file mode 100644 index 00000000..05d6e2b1 --- /dev/null +++ b/src/plugins/nokia/mm-sim-nokia.h @@ -0,0 +1,51 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_SIM_NOKIA_H +#define MM_SIM_NOKIA_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-sim.h" + +#define MM_TYPE_SIM_NOKIA (mm_sim_nokia_get_type ()) +#define MM_SIM_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_NOKIA, MMSimNokia)) +#define MM_SIM_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_NOKIA, MMSimNokiaClass)) +#define MM_IS_SIM_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_NOKIA)) +#define MM_IS_SIM_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_NOKIA)) +#define MM_SIM_NOKIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_NOKIA, MMSimNokiaClass)) + +typedef struct _MMSimNokia MMSimNokia; +typedef struct _MMSimNokiaClass MMSimNokiaClass; + +struct _MMSimNokia { + MMBaseSim parent; +}; + +struct _MMSimNokiaClass { + MMBaseSimClass parent; +}; + +GType mm_sim_nokia_get_type (void); + +void mm_sim_nokia_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_sim_nokia_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_SIM_NOKIA_H */ diff --git a/src/plugins/novatel/mm-broadband-bearer-novatel-lte.c b/src/plugins/novatel/mm-broadband-bearer-novatel-lte.c new file mode 100644 index 00000000..cd3296e2 --- /dev/null +++ b/src/plugins/novatel/mm-broadband-bearer-novatel-lte.c @@ -0,0 +1,580 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2011 - 2012 Google, Inc. + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-novatel-lte.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" + +#define QMISTATUS_TAG "$NWQMISTATUS:" + +G_DEFINE_TYPE (MMBroadbandBearerNovatelLte, mm_broadband_bearer_novatel_lte, MM_TYPE_BROADBAND_BEARER) + +/*****************************************************************************/ + +static gchar * +normalize_qmistatus (const gchar *status) +{ + gchar *normalized_status, *iter; + + if (!status) + return NULL; + + normalized_status = g_strdup (status); + for (iter = normalized_status; *iter; iter++) + if (g_ascii_isspace (*iter)) + *iter = ' '; + + return normalized_status; +} + +static gboolean +is_qmistatus_connected (const gchar *str) +{ + str = mm_strip_tag (str, QMISTATUS_TAG); + + return g_strrstr (str, "QMI State: CONNECTED") || g_strrstr (str, "QMI State: QMI_WDS_PKT_DATA_CONNECTED"); +} + +static gboolean +is_qmistatus_disconnected (const gchar *str) +{ + str = mm_strip_tag (str, QMISTATUS_TAG); + + return g_strrstr (str, "QMI State: DISCONNECTED") || g_strrstr (str, "QMI State: QMI_WDS_PKT_DATA_DISCONNECTED"); +} + +static gboolean +is_qmistatus_call_failed (const gchar *str) +{ + str = mm_strip_tag (str, QMISTATUS_TAG); + + return (g_strrstr (str, "QMI_RESULT_FAILURE:QMI_ERR_CALL_FAILED") != NULL); +} + +/*****************************************************************************/ +/* Connection status monitoring */ + +static MMBearerConnectionStatus +load_connection_status_finish (MMBaseBearer *bearer, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_BEARER_CONNECTION_STATUS_UNKNOWN; + } + return (MMBearerConnectionStatus)value; +} + +static void +poll_connection_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + const gchar *result; + GError *error = NULL; + + result = mm_base_modem_at_command_finish (modem, res, &error); + if (!result) + g_task_return_error (task, error); + else if (is_qmistatus_disconnected (result)) + g_task_return_int (task, MM_BEARER_CONNECTION_STATUS_DISCONNECTED); + else + g_task_return_int (task, MM_BEARER_CONNECTION_STATUS_CONNECTED); + g_object_unref (task); +} + +static void +load_connection_status (MMBaseBearer *bearer, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMBaseModem *modem = NULL; + + task = g_task_new (bearer, NULL, callback, user_data); + + g_object_get (MM_BASE_BEARER (bearer), + MM_BASE_BEARER_MODEM, &modem, + NULL); + + mm_base_modem_at_command ( + modem, + "$NWQMISTATUS", + 3, + FALSE, + (GAsyncReadyCallback) poll_connection_ready, + task); + + g_object_unref (modem); +} + +/*****************************************************************************/ +/* 3GPP Connection sequence */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + MMPort *data; + gint retries; +} DetailedConnectContext; + +static void +detailed_connect_context_free (DetailedConnectContext *ctx) +{ + if (ctx->data) + g_object_unref (ctx->data); + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_slice_free (DetailedConnectContext, ctx); +} + +static MMBearerConnectResult * +connect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static gboolean connect_3gpp_qmistatus (GTask *task); + +static void +connect_3gpp_qmistatus_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerNovatelLte *self; + DetailedConnectContext *ctx; + const gchar *result; + gchar *normalized_result; + GError *error = NULL; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + result = mm_base_modem_at_command_full_finish (modem, res, &error); + if (!result) { + if (!g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + mm_obj_dbg (self, "connection status failed: %s; will retry", error->message); + g_error_free (error); + goto retry; + } + + if (is_qmistatus_connected (result)) { + MMBearerIpConfig *config; + + mm_obj_dbg (self, "connected"); + config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP); + g_task_return_pointer ( + task, + mm_bearer_connect_result_new (ctx->data, config, NULL), + (GDestroyNotify)mm_bearer_connect_result_unref); + g_object_unref (task); + g_object_unref (config); + return; + } + + /* Don't retry if the call failed */ + if (is_qmistatus_call_failed (result)) { + mm_obj_dbg (self, "not retrying: call failed"); + ctx->retries = 0; + } + +retry: + if (ctx->retries > 0) { + ctx->retries--; + mm_obj_dbg (self, "retrying status check in a second: %d retries left", ctx->retries); + g_timeout_add_seconds (1, (GSourceFunc)connect_3gpp_qmistatus, task); + return; + } + + /* Already exhausted all retries */ + normalized_result = normalize_qmistatus (result); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "QMI connect failed: %s", + normalized_result); + g_object_unref (task); + g_free (normalized_result); +} + +static gboolean +connect_3gpp_qmistatus (GTask *task) +{ + DetailedConnectContext *ctx; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_full ( + ctx->modem, + ctx->primary, + "$NWQMISTATUS", + 3, /* timeout */ + FALSE, /* allow_cached */ + FALSE, /* is_raw */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)connect_3gpp_qmistatus_ready, /* callback */ + task); /* user_data */ + + return G_SOURCE_REMOVE; +} + +static void +connect_3gpp_qmiconnect_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + const gchar *result; + GError *error = NULL; + + result = mm_base_modem_at_command_full_finish (modem, res, &error); + if (!result) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* + * The connection takes a bit of time to set up, but there's no + * asynchronous notification from the modem when this has + * happened. Instead, we need to poll the modem to see if it's + * ready. + */ + g_timeout_add_seconds (1, (GSourceFunc)connect_3gpp_qmistatus, task); +} + +static void +connect_3gpp_authenticate (GTask *task) +{ + MMBroadbandBearerNovatelLte *self; + DetailedConnectContext *ctx; + MMBearerProperties *config; + gchar *command, *apn, *user, *password; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + config = mm_base_bearer_peek_config (MM_BASE_BEARER (self)); + apn = mm_port_serial_at_quote_string (mm_bearer_properties_get_apn (config)); + user = mm_port_serial_at_quote_string (mm_bearer_properties_get_user (config)); + password = mm_port_serial_at_quote_string (mm_bearer_properties_get_password (config)); + command = g_strdup_printf ("$NWQMICONNECT=,,,,,,%s,,,%s,%s", + apn, user, password); + g_free (apn); + g_free (user); + g_free (password); + mm_base_modem_at_command_full ( + ctx->modem, + ctx->primary, + command, + 10, /* timeout */ + FALSE, /* allow_cached */ + FALSE, /* is_raw */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)connect_3gpp_qmiconnect_ready, + task); /* user_data */ + g_free (command); +} + +static void +connect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DetailedConnectContext *ctx; + GTask *task; + + ctx = g_slice_new0 (DetailedConnectContext); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + ctx->retries = MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_connect_context_free); + + /* Get a 'net' data port */ + ctx->data = mm_base_modem_get_best_data_port (ctx->modem, MM_PORT_TYPE_NET); + if (!ctx->data) { + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_CONNECTED, + "Couldn't connect: no available net port available"); + g_object_unref (task); + return; + } + + connect_3gpp_authenticate (task); +} + +/*****************************************************************************/ +/* 3GPP Disonnection sequence */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + MMPort *data; + gint retries; +} DetailedDisconnectContext; + +static void +detailed_disconnect_context_free (DetailedDisconnectContext *ctx) +{ + g_object_unref (ctx->data); + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_free (ctx); +} + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean disconnect_3gpp_qmistatus (GTask *task); + +static void +disconnect_3gpp_status_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerNovatelLte *self; + DetailedDisconnectContext *ctx; + const gchar *result; + GError *error = NULL; + gboolean is_connected = FALSE; + + self = g_task_get_source_object (task); + + result = mm_base_modem_at_command_full_finish (modem, res, &error); + if (result) { + mm_obj_dbg (self, "QMI connection status: %s", result); + if (is_qmistatus_disconnected (result)) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + if (is_qmistatus_connected (result)) + is_connected = TRUE; + } else { + mm_obj_dbg (self, "QMI connection status failed: %s", error->message); + g_error_free (error); + result = "Unknown error"; + } + + ctx = g_task_get_task_data (task); + + if (ctx->retries > 0) { + ctx->retries--; + mm_obj_dbg (self, "retrying status check in a second: %d retries left", ctx->retries); + g_timeout_add_seconds (1, (GSourceFunc)disconnect_3gpp_qmistatus, task); + return; + } + + /* If $NWQMISTATUS reports a CONNECTED QMI state, returns an error such that + * the modem state remains 'connected'. Otherwise, assumes the modem is + * disconnected from the network successfully. */ + if (is_connected) { + gchar *normalized_result; + + normalized_result = normalize_qmistatus (result); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "QMI disconnect failed: %s", + normalized_result); + g_free (normalized_result); + } else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static gboolean +disconnect_3gpp_qmistatus (GTask *task) +{ + DetailedDisconnectContext *ctx; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_full ( + ctx->modem, + ctx->primary, + "$NWQMISTATUS", + 3, /* timeout */ + FALSE, /* allow_cached */ + FALSE, /* is_raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)disconnect_3gpp_status_ready, + task); /* user_data */ + return G_SOURCE_REMOVE; +} + + +static void +disconnect_3gpp_check_status (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerNovatelLte *self; + GError *error = NULL; + + self = g_task_get_source_object (task); + + mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + mm_obj_dbg (self, "disconnection error: %s", error->message); + g_error_free (error); + } + + disconnect_3gpp_qmistatus (task); +} + +static void +disconnect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DetailedDisconnectContext *ctx; + GTask *task; + + ctx = g_new0 (DetailedDisconnectContext, 1); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + ctx->data = g_object_ref (data); + ctx->retries = MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_disconnect_context_free); + + mm_base_modem_at_command_full ( + ctx->modem, + ctx->primary, + "$NWQMIDISCONNECT", + 10, /* timeout */ + FALSE, /* allow_cached */ + FALSE, /* is_raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)disconnect_3gpp_check_status, + task); /* user_data */ +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_broadband_bearer_novatel_lte_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *bearer; + GObject *source; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_novatel_lte_new (MMBroadbandModemNovatelLte *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + NULL); +} + +static void +mm_broadband_bearer_novatel_lte_init (MMBroadbandBearerNovatelLte *self) +{ +} + +static void +mm_broadband_bearer_novatel_lte_class_init (MMBroadbandBearerNovatelLteClass *klass) +{ + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + base_bearer_class->load_connection_status = load_connection_status; + base_bearer_class->load_connection_status_finish = load_connection_status_finish; +#if defined WITH_SUSPEND_RESUME + base_bearer_class->reload_connection_status = load_connection_status; + base_bearer_class->reload_connection_status_finish = load_connection_status_finish; +#endif + + broadband_bearer_class->connect_3gpp = connect_3gpp; + broadband_bearer_class->connect_3gpp_finish = connect_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; +} diff --git a/src/plugins/novatel/mm-broadband-bearer-novatel-lte.h b/src/plugins/novatel/mm-broadband-bearer-novatel-lte.h new file mode 100644 index 00000000..4f503865 --- /dev/null +++ b/src/plugins/novatel/mm-broadband-bearer-novatel-lte.h @@ -0,0 +1,60 @@ +/* -*- 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: + * + * Author: Nathan Williams <njw@google.com> + * + * Copyright (C) 2012 Google, Inc. + */ + +#ifndef MM_BROADBAND_BEARER_NOVATEL_LTE_H +#define MM_BROADBAND_BEARER_NOVATEL_LTE_H + +#include <glib.h> +#include <glib-object.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-novatel-lte.h" + +#define MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE (mm_broadband_bearer_novatel_lte_get_type ()) +#define MM_BROADBAND_BEARER_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE, MMBroadbandBearerNovatelLte)) +#define MM_BROADBAND_BEARER_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE, MMBroadbandBearerNovatelLteClass)) +#define MM_IS_BROADBAND_BEARER_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE)) +#define MM_IS_BROADBAND_BEARER_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE)) +#define MM_BROADBAND_BEARER_NOVATEL_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE, MMBroadbandBearerNovatelLteClass)) + +typedef struct _MMBroadbandBearerNovatelLte MMBroadbandBearerNovatelLte; +typedef struct _MMBroadbandBearerNovatelLteClass MMBroadbandBearerNovatelLteClass; + +struct _MMBroadbandBearerNovatelLte { + MMBroadbandBearer parent; +}; + +struct _MMBroadbandBearerNovatelLteClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_novatel_lte_get_type (void); + +/* Default 3GPP bearer creation implementation */ +void mm_broadband_bearer_novatel_lte_new (MMBroadbandModemNovatelLte *modem, + MMBearerProperties *properties, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_novatel_lte_new_finish (GAsyncResult *res, + GError **error); + + +#endif /* MM_BROADBAND_BEARER_NOVATEL_LTE_H */ diff --git a/src/plugins/novatel/mm-broadband-modem-novatel-lte.c b/src/plugins/novatel/mm-broadband-modem-novatel-lte.c new file mode 100644 index 00000000..19d1c594 --- /dev/null +++ b/src/plugins/novatel/mm-broadband-modem-novatel-lte.c @@ -0,0 +1,701 @@ +/* -*- 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) 2012 Google Inc. + * Author: Nathan Williams <njw@google.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-novatel-lte.h" +#include "mm-broadband-modem-novatel-lte.h" +#include "mm-sim-novatel-lte.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-messaging.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-serial-parsers.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); + +static MMIfaceModem3gpp *iface_modem_3gpp_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemNovatelLte, mm_broadband_modem_novatel_lte, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)); + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=4", + 6, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_novatel_lte_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + g_object_unref (task); +} + +static void +modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* We just create a MMBroadbandBearer */ + mm_broadband_bearer_novatel_lte_new (MM_BROADBAND_MODEM_NOVATEL_LTE (self), + properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_new_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Create SIM (Modem interface) */ + +static MMBaseSim * +modem_create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_novatel_lte_new_finish (res, error); +} + +static void +modem_create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* New Novatel LTE SIM */ + mm_sim_novatel_lte_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + +/*****************************************************************************/ +/* After SIM unlock (Modem interface) */ + +static gboolean +modem_after_sim_unlock_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +after_sim_unlock_wait_cb (GTask *task) +{ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +modem_after_sim_unlock (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* A 3-second wait is necessary for SIM to become ready. + * Otherwise, a subsequent AT+CRSM command will likely fail. */ + g_timeout_add_seconds (3, (GSourceFunc)after_sim_unlock_wait_cb, task); +} + +/*****************************************************************************/ +/* Load own numbers (Modem interface) */ + +static GStrv +load_own_numbers_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + GVariant *result; + GStrv own_numbers; + + result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error); + if (!result) + return NULL; + + own_numbers = (GStrv) g_variant_dup_strv (result, NULL); + return own_numbers; +} + +static MMBaseModemAtResponseProcessorResult +response_processor_cnum_ignore_at_errors (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + GStrv own_numbers; + + *result = NULL; + *result_error = NULL; + + if (error) { + /* 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; + } + + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + } + + own_numbers = mm_3gpp_parse_cnum_exec_response (response); + if (!own_numbers) + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + + *result = g_variant_new_strv ((const gchar *const *) own_numbers, -1); + g_strfreev (own_numbers); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; +} + +static MMBaseModemAtResponseProcessorResult +response_processor_nwmdn_ignore_at_errors (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + g_auto(GStrv) own_numbers = NULL; + GPtrArray *array; + gchar *mdn; + + *result = NULL; + *result_error = NULL; + + if (error) { + /* 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; + } + + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + } + + mdn = g_strdup (mm_strip_tag (response, "$NWMDN:")); + + array = g_ptr_array_new (); + g_ptr_array_add (array, mdn); + g_ptr_array_add (array, NULL); + own_numbers = (GStrv) g_ptr_array_free (array, FALSE); + + *result = g_variant_new_strv ((const gchar *const *) own_numbers, -1); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; +} + +static const MMBaseModemAtCommand own_numbers_commands[] = { + { "+CNUM", 3, TRUE, response_processor_cnum_ignore_at_errors }, + { "$NWMDN", 3, TRUE, response_processor_nwmdn_ignore_at_errors }, + { NULL } +}; + +static void +load_own_numbers (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_sequence ( + MM_BASE_MODEM (self), + own_numbers_commands, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + callback, + user_data); +} + +/*****************************************************************************/ +/* Load supported bands (Modem interface) */ + +/* + * Mapping from bits set in response of $NWBAND? command to MMModemBand values. + * The bit positions and band names on the right come from the response to $NWBAND=? + */ +static MMModemBand bandbits[] = { + MM_MODEM_BAND_CDMA_BC0, /* "00 CDMA2000 Band Class 0, A-System" */ + MM_MODEM_BAND_CDMA_BC0, /* "01 CDMA2000 Band Class 0, B-System" */ + MM_MODEM_BAND_CDMA_BC1, /* "02 CDMA2000 Band Class 1, all blocks" */ + MM_MODEM_BAND_CDMA_BC2, /* "03 CDMA2000 Band Class 2, place holder" */ + MM_MODEM_BAND_CDMA_BC3, /* "04 CDMA2000 Band Class 3, A-System" */ + MM_MODEM_BAND_CDMA_BC4, /* "05 CDMA2000 Band Class 4, all blocks" */ + MM_MODEM_BAND_CDMA_BC5, /* "06 CDMA2000 Band Class 5, all blocks" */ + MM_MODEM_BAND_DCS, /* "07 GSM DCS band" */ + MM_MODEM_BAND_EGSM, /* "08 GSM Extended GSM (E-GSM) band" */ + MM_MODEM_BAND_UNKNOWN, /* "09 GSM Primary GSM (P-GSM) band" */ + MM_MODEM_BAND_CDMA_BC6, /* "10 CDMA2000 Band Class 6" */ + MM_MODEM_BAND_CDMA_BC7, /* "11 CDMA2000 Band Class 7" */ + MM_MODEM_BAND_CDMA_BC8, /* "12 CDMA2000 Band Class 8" */ + MM_MODEM_BAND_CDMA_BC9, /* "13 CDMA2000 Band Class 9" */ + MM_MODEM_BAND_CDMA_BC10, /* "14 CDMA2000 Band Class 10 */ + MM_MODEM_BAND_CDMA_BC11, /* "15 CDMA2000 Band Class 11 */ + MM_MODEM_BAND_G450, /* "16 GSM 450 band" */ + MM_MODEM_BAND_G480, /* "17 GSM 480 band" */ + MM_MODEM_BAND_G750, /* "18 GSM 750 band" */ + MM_MODEM_BAND_G850, /* "19 GSM 850 band" */ + MM_MODEM_BAND_UNKNOWN, /* "20 GSM 900 Railways band" */ + MM_MODEM_BAND_PCS, /* "21 GSM PCS band" */ + MM_MODEM_BAND_UTRAN_1, /* "22 WCDMA I IMT 2000 band" */ + MM_MODEM_BAND_UTRAN_2, /* "23 WCDMA II PCS band" */ + MM_MODEM_BAND_UTRAN_3, /* "24 WCDMA III 1700 band" */ + MM_MODEM_BAND_UTRAN_4, /* "25 WCDMA IV 1700 band" */ + MM_MODEM_BAND_UTRAN_5, /* "26 WCDMA V US850 band" */ + MM_MODEM_BAND_UTRAN_6, /* "27 WCDMA VI JAPAN 800 band" */ + MM_MODEM_BAND_UNKNOWN, /* "28 Reserved for BC12/BC14 */ + MM_MODEM_BAND_UNKNOWN, /* "29 Reserved for BC12/BC14 */ + MM_MODEM_BAND_UNKNOWN, /* "30 Reserved" */ + MM_MODEM_BAND_UNKNOWN, /* "31 Reserved" */ +}; + +static GArray * +load_supported_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_supported_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + GArray *bands; + guint i; + + task = g_task_new (self, NULL, callback, user_data); + + /* + * The modem doesn't support telling us what bands are supported; + * list everything we know about. + */ + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23); + for (i = 0 ; i < G_N_ELEMENTS (bandbits) ; i++) { + if (bandbits[i] != MM_MODEM_BAND_UNKNOWN) + g_array_append_val(bands, bandbits[i]); + } + + g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Load current bands (Modem interface) */ + +static GArray * +load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_current_bands_done (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GArray *bands; + const gchar *response; + GError *error = NULL; + guint i; + guint32 bandval; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* + * Response is "$NWBAND: <hex value>", where the hex value is a + * bitmask of supported bands. + */ + bandval = (guint32)strtoul(response + 9, NULL, 16); + + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4); + for (i = 0 ; i < G_N_ELEMENTS (bandbits) ; i++) { + if ((bandval & (1 << i)) && bandbits[i] != MM_MODEM_BAND_UNKNOWN) + g_array_append_val(bands, bandbits[i]); + } + + g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); + g_object_unref (task); +} + +static void +load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "$NWBAND?", + 3, + FALSE, + (GAsyncReadyCallback)load_current_bands_done, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_unlock_retries_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + gint pin_num, pin_value; + int scan_count; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + response = mm_strip_tag (response, "$NWPINR:"); + + scan_count = sscanf (response, "PIN%d, %d", &pin_num, &pin_value); + if (scan_count != 2 || (pin_num != 1 && pin_num != 2)) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid unlock retries response: '%s'", + response); + } else { + MMUnlockRetries *retries; + + retries = mm_unlock_retries_new (); + mm_unlock_retries_set (retries, + pin_num == 1 ? MM_MODEM_LOCK_SIM_PIN : MM_MODEM_LOCK_SIM_PIN2, + pin_value); + g_task_return_pointer (task, retries, g_object_unref); + } + g_object_unref (task); +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "$NWPINR?", + 20, + FALSE, + (GAsyncReadyCallback)load_unlock_retries_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + *access_technologies = (MMModemAccessTechnology) value; + *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; + return TRUE; +} + +static void +load_access_technologies_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + MMModemAccessTechnology act; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + if (strstr (response, "LTE")) + act |= MM_MODEM_ACCESS_TECHNOLOGY_LTE; + if (strstr (response, "WCDMA")) + act |= MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + if (strstr (response, "EV-DO Rev 0")) + act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; + if (strstr (response, "EV-DO Rev A")) + act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDOA; + if (strstr (response, "CDMA 1X")) + act |= MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; + if (strstr (response, "GSM")) + act |= MM_MODEM_ACCESS_TECHNOLOGY_GSM; + + g_task_return_int (task, act); + g_object_unref (task); +} + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "$NWSYSMODE", + 3, + FALSE, + (GAsyncReadyCallback)load_access_technologies_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Reset (Modem interface) */ + +static gboolean +reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=6", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Scan networks (3GPP interface) */ + +static GList * +scan_networks_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_scan_networks_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GList *scan_result; + + scan_result = iface_modem_3gpp_parent->scan_networks_finish (self, res, &error); + if (!scan_result) + g_task_return_error (task, error); + else + g_task_return_pointer (task, + scan_result, + (GDestroyNotify)mm_3gpp_network_info_list_free); + g_object_unref (task); +} + +static void +scan_networks (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMModemAccessTechnology access_tech; + + mm_obj_dbg (self, "scanning for networks (Novatel LTE)..."); + + task = g_task_new (self, NULL, callback, user_data); + + /* The Novatel LTE modem does not properly support AT+COPS=? in LTE mode. + * Thus, do not try to scan networks when the current access technologies + * include LTE. + */ + access_tech = mm_iface_modem_get_access_technologies (MM_IFACE_MODEM (self)); + if (access_tech & MM_MODEM_ACCESS_TECHNOLOGY_LTE) { + g_autofree gchar *access_tech_string = NULL; + + access_tech_string = mm_modem_access_technology_build_string_from_mask (access_tech); + mm_obj_warn (self, "couldn't scan for networks with access technologies: %s", access_tech_string); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Couldn't scan for networks with access technologies: %s", + access_tech_string); + g_object_unref (task); + return; + } + + /* Otherwise, just fallback to the generic scan method */ + iface_modem_3gpp_parent->scan_networks (self, + (GAsyncReadyCallback)parent_scan_networks_ready, + task); +} + +/*****************************************************************************/ + +MMBroadbandModemNovatelLte * +mm_broadband_modem_novatel_lte_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Novatel LTE bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + NULL); +} + +static void +mm_broadband_modem_novatel_lte_init (MMBroadbandModemNovatelLte *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; + iface->create_sim = modem_create_sim; + iface->create_sim_finish = modem_create_sim_finish; + iface->modem_after_sim_unlock = modem_after_sim_unlock; + iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish; + iface->load_own_numbers = load_own_numbers; + iface->load_own_numbers_finish = load_own_numbers_finish; + iface->load_supported_bands = load_supported_bands; + iface->load_supported_bands_finish = load_supported_bands_finish; + iface->load_current_bands = load_current_bands; + iface->load_current_bands_finish = load_current_bands_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; + /* No support for setting bands, as it destabilizes the modem. */ + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->reset = reset; + iface->reset_finish = reset_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->scan_networks = scan_networks; + iface->scan_networks_finish = scan_networks_finish; +} + +static void +mm_broadband_modem_novatel_lte_class_init (MMBroadbandModemNovatelLteClass *klass) +{ +} diff --git a/src/plugins/novatel/mm-broadband-modem-novatel-lte.h b/src/plugins/novatel/mm-broadband-modem-novatel-lte.h new file mode 100644 index 00000000..0f64339d --- /dev/null +++ b/src/plugins/novatel/mm-broadband-modem-novatel-lte.h @@ -0,0 +1,50 @@ +/* -*- 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) 2012 Google Inc. + * Author: Nathan Williams <njw@google.com> + */ + +#ifndef MM_BROADBAND_MODEM_NOVATEL_LTE_H +#define MM_BROADBAND_MODEM_NOVATEL_LTE_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE (mm_broadband_modem_novatel_lte_get_type ()) +#define MM_BROADBAND_MODEM_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE, MMBroadbandModemNovatelLte)) +#define MM_BROADBAND_MODEM_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE, MMBroadbandModemNovatelLteClass)) +#define MM_IS_BROADBAND_MODEM_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE)) +#define MM_IS_BROADBAND_MODEM_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE)) +#define MM_BROADBAND_MODEM_NOVATEL_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE, MMBroadbandModemNovatelLteClass)) + +typedef struct _MMBroadbandModemNovatelLte MMBroadbandModemNovatelLte; +typedef struct _MMBroadbandModemNovatelLteClass MMBroadbandModemNovatelLteClass; +typedef struct _MMBroadbandModemNovatelLtePrivate MMBroadbandModemNovatelLtePrivate; + +struct _MMBroadbandModemNovatelLte { + MMBroadbandModem parent; + MMBroadbandModemNovatelLtePrivate *priv; +}; + +struct _MMBroadbandModemNovatelLteClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_novatel_lte_get_type (void); + +MMBroadbandModemNovatelLte *mm_broadband_modem_novatel_lte_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_NOVATEL_LTE_H */ diff --git a/src/plugins/novatel/mm-broadband-modem-novatel.c b/src/plugins/novatel/mm-broadband-modem-novatel.c new file mode 100644 index 00000000..cd8e5676 --- /dev/null +++ b/src/plugins/novatel/mm-broadband-modem-novatel.c @@ -0,0 +1,1609 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-cdma.h" +#include "mm-iface-modem-time.h" +#include "mm-iface-modem-messaging.h" +#include "mm-broadband-modem-novatel.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers.h" +#include "mm-common-helpers.h" +#include "libqcdm/src/commands.h" +#include "libqcdm/src/result.h" +#include "mm-log-object.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_messaging_init (MMIfaceModemMessaging *iface); +static void iface_modem_cdma_init (MMIfaceModemCdma *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); + +static MMIfaceModem *iface_modem_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemNovatel, mm_broadband_modem_novatel, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)) + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMModemModeCombination mode; + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G, 2G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + /* 2G and 3G, 3G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +typedef struct { + MMModemMode allowed; + MMModemMode preferred; +} LoadCurrentModesResult; + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + LoadCurrentModesResult *result; + + result = g_task_propagate_pointer (G_TASK (res), error); + if (!result) + return FALSE; + + *allowed = result->allowed; + *preferred = result->preferred; + g_free (result); + + return TRUE; +} + +static void +nwrat_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LoadCurrentModesResult *result; + GError *error = NULL; + const gchar *response; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + gint a = -1; + gint b = -1; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Parse response */ + r = g_regex_new ("\\$NWRAT:\\s*(\\d),(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &error)) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match NWRAT reply: %s", + response); + g_object_unref (task); + return; + } + + if (!mm_get_int_from_match_info (match_info, 1, &a) || + !mm_get_int_from_match_info (match_info, 2, &b) || + a < 0 || a > 2 || + b < 1 || b > 2) { + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse mode/tech response '%s': invalid modes reported", + response); + g_object_unref (task); + return; + } + + result = g_new0 (LoadCurrentModesResult, 1); + + switch (a) { + case 0: + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + result->preferred = MM_MODEM_MODE_NONE; + break; + case 1: + if (b == 1) { + result->allowed = MM_MODEM_MODE_2G; + result->preferred = MM_MODEM_MODE_NONE; + } else /* b == 2 */ { + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + result->preferred = MM_MODEM_MODE_2G; + } + break; + case 2: + if (b == 1) { + result->allowed = MM_MODEM_MODE_3G; + result->preferred = MM_MODEM_MODE_NONE; + } else /* b == 2 */ { + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + result->preferred = MM_MODEM_MODE_3G; + } + break; + default: + /* We only allow mode 0|1|2 */ + g_assert_not_reached (); + break; + } + + g_task_return_pointer (task, result, g_free); + g_object_unref (task); +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Load allowed modes only in 3GPP modems */ + if (!mm_iface_modem_is_3gpp (self)) { + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Loading allowed modes not supported in CDMA-only modems"); + g_object_unref (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "$NWRAT?", + 3, + FALSE, + (GAsyncReadyCallback)nwrat_query_ready, + task); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +allowed_mode_update_ready (MMBroadbandModemNovatel *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (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); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *command; + gint a = -1; + gint b = -1; + + task = g_task_new (self, NULL, callback, user_data); + + /* Setting allowed modes only in 3GPP modems */ + if (!mm_iface_modem_is_3gpp (self)) { + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Setting allowed modes not supported in CDMA-only modems"); + g_object_unref (task); + return; + } + + if (allowed == MM_MODEM_MODE_2G) { + a = 1; + b = 1; + } else if (allowed == MM_MODEM_MODE_3G) { + a = 2; + b = 1; + } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { + b = 2; + if (preferred == MM_MODEM_MODE_NONE) + a = 0; + else if (preferred == MM_MODEM_MODE_2G) + a = 1; + else if (preferred == MM_MODEM_MODE_3G) + a = 2; + } else if (allowed == MM_MODEM_MODE_ANY && + preferred == MM_MODEM_MODE_NONE) { + b = 2; + a = 0; + } + + if (a < 0 || b < 0) { + 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_object_unref (task); + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("AT$NWRAT=%d,%d", a, b); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)allowed_mode_update_ready, + task); + g_free (command); +} + +/*****************************************************************************/ + +static void +close_and_unref_port (MMPortSerialQcdm *port) +{ + mm_port_serial_close (MM_PORT_SERIAL (port)); + g_object_unref (port); +} + +static gboolean +get_evdo_version_finish (MMBaseModem *self, + GAsyncResult *res, + guint *hdr_revision, /* QCDM_HDR_REV_* */ + GError **error) +{ + gssize result; + + result = g_task_propagate_int (G_TASK (res), error); + if (result < 0) + return FALSE; + + *hdr_revision = (guint8) result; + return TRUE; +} + +static void +nw_snapshot_old_ready (MMPortSerialQcdm *port, + GAsyncResult *res, + GTask *task) +{ + QcdmResult *result; + GError *error = NULL; + GByteArray *response; + guint8 hdr_revision = QCDM_HDR_REV_UNKNOWN; + + response = mm_port_serial_qcdm_command_finish (port, res, &error); + if (error) { + /* Just ignore the error and complete with the input info */ + g_prefix_error (&error, "Couldn't run QCDM Novatel Modem MSM6500 snapshot: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Parse the response */ + result = qcdm_cmd_nw_subsys_modem_snapshot_cdma_result ((const gchar *) response->data, response->len, NULL); + g_byte_array_unref (response); + if (!result) { + g_prefix_error (&error, "Failed to get QCDM Novatel Modem MSM6500 snapshot: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Success */ + qcdm_result_get_u8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_HDR_REV, &hdr_revision); + qcdm_result_unref (result); + + g_task_return_int (task, (gint) hdr_revision); + g_object_unref (task); +} + +static void +nw_snapshot_new_ready (MMPortSerialQcdm *port, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemNovatel *self; + QcdmResult *result; + GByteArray *nwsnap; + GError *error = NULL; + GByteArray *response; + guint8 hdr_revision = QCDM_HDR_REV_UNKNOWN; + + self = g_task_get_source_object (task); + + response = mm_port_serial_qcdm_command_finish (port, res, &error); + if (error) { + g_prefix_error (&error, "couldn't run QCDM Novatel Modem MSM6800 snapshot: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Parse the response */ + result = qcdm_cmd_nw_subsys_modem_snapshot_cdma_result ((const gchar *) response->data, response->len, NULL); + g_byte_array_unref (response); + if (result) { + /* Success */ + qcdm_result_get_u8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_HDR_REV, &hdr_revision); + qcdm_result_unref (result); + + g_task_return_int (task, (gint) hdr_revision); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "failed to get QCDM Novatel Modem MSM6800 snapshot"); + + /* Try for MSM6500 */ + nwsnap = g_byte_array_sized_new (25); + nwsnap->len = qcdm_cmd_nw_subsys_modem_snapshot_cdma_new ((char *) nwsnap->data, 25, QCDM_NW_CHIPSET_6500); + g_assert (nwsnap->len); + mm_port_serial_qcdm_command (port, + nwsnap, + 3, + NULL, + (GAsyncReadyCallback)nw_snapshot_old_ready, + task); + g_byte_array_unref (nwsnap); +} + +static void +get_evdo_version (MMBaseModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GError *error = NULL; + GByteArray *nwsnap; + GTask *task; + MMPortSerialQcdm *port; + + task = g_task_new (self, NULL, callback, user_data); + + port = mm_base_modem_get_port_qcdm (self); + if (!port) { + error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No available QCDM port"); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + g_task_set_task_data (task, port, (GDestroyNotify) close_and_unref_port); + + if (!mm_port_serial_open (MM_PORT_SERIAL (port), &error)) { + g_prefix_error (&error, "couldn't open QCDM port: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Try MSM6800 first since newer cards use that */ + nwsnap = g_byte_array_sized_new (25); + nwsnap->len = qcdm_cmd_nw_subsys_modem_snapshot_cdma_new ((char *) nwsnap->data, 25, QCDM_NW_CHIPSET_6800); + g_assert (nwsnap->len); + mm_port_serial_qcdm_command (port, + nwsnap, + 3, + NULL, + (GAsyncReadyCallback)nw_snapshot_new_ready, + task); + g_byte_array_unref (nwsnap); +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +typedef struct { + MMModemAccessTechnology act; + guint mask; + guint hdr_revision; /* QCDM_HDR_REV_* */ +} AccessTechContext; + +static gboolean +modem_load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + AccessTechContext *ctx; + + if (!g_task_propagate_boolean (G_TASK (res), error)) + return FALSE; + + /* Update access technology with specific EVDO revision from QCDM if we have them */ + ctx = g_task_get_task_data (G_TASK (res)); + if (ctx->act & MM_IFACE_MODEM_CDMA_ALL_EVDO_ACCESS_TECHNOLOGIES_MASK) { + if (ctx->hdr_revision == QCDM_HDR_REV_0) { + mm_obj_dbg (self, "modem snapshot EVDO revision: 0"); + ctx->act &= ~MM_IFACE_MODEM_CDMA_ALL_EVDO_ACCESS_TECHNOLOGIES_MASK; + ctx->act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; + } else if (ctx->hdr_revision == QCDM_HDR_REV_A) { + mm_obj_dbg (self, "modem snapshot EVDO revision: A"); + ctx->act &= ~MM_IFACE_MODEM_CDMA_ALL_EVDO_ACCESS_TECHNOLOGIES_MASK; + ctx->act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDOA; + } else + mm_obj_dbg (self, "modem snapshot EVDO revision: %d (unknown)", ctx->hdr_revision); + } + + *access_technologies = ctx->act; + *mask = ctx->mask; + return TRUE; +} + +static void +cnti_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + AccessTechContext *ctx = g_task_get_task_data (task); + GError *error = NULL; + const gchar *response; + const gchar *p; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + p = mm_strip_tag (response, "$CNTI:"); + p = strchr (p, ','); + if (!p) { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse $CNTI result '%s'", + response); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx->act = mm_string_to_access_tech (p); + ctx->mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +evdo_version_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + AccessTechContext *ctx = g_task_get_task_data (task); + + if (!get_evdo_version_finish (self, res, &ctx->hdr_revision, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_load_access_technologies_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + AccessTechContext *ctx = g_task_get_task_data (task); + + if (!iface_modem_parent->load_access_technologies_finish (self, + res, + &ctx->act, + &ctx->mask, + &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* No point in checking EVDO revision if EVDO isn't being used */ + if (!(ctx->act & MM_IFACE_MODEM_CDMA_ALL_EVDO_ACCESS_TECHNOLOGIES_MASK)) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Get the EVDO revision from QCDM */ + get_evdo_version (MM_BASE_MODEM (self), + (GAsyncReadyCallback) evdo_version_ready, + task); +} + +static void +modem_load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + AccessTechContext *ctx; + GTask *task; + + /* Setup context */ + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_new0 (AccessTechContext, 1); + g_task_set_task_data (task, ctx, g_free); + + /* CDMA-only modems defer to parent for generic access technology + * checking, but can determine EVDOr0 vs. EVDOrA through proprietary + * QCDM commands. + */ + if (mm_iface_modem_is_cdma_only (self)) { + iface_modem_parent->load_access_technologies ( + self, + (GAsyncReadyCallback)parent_load_access_technologies_ready, + task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "$CNTI=0", + 3, + FALSE, + (GAsyncReadyCallback)cnti_set_ready, + task); +} + +/*****************************************************************************/ +/* Signal quality loading (Modem interface) */ + +static guint +modem_load_signal_quality_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return 0; + } + return (guint)value; +} + +static void +parent_load_signal_quality_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + guint signal_quality; + + signal_quality = iface_modem_parent->load_signal_quality_finish (self, res, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_int (task, signal_quality); + g_object_unref (task); +} + +static gint +get_one_quality (const gchar *reply, + const gchar *tag) +{ + gint quality = -1; + char *temp, *p; + gint dbm; + gboolean success = FALSE; + + p = strstr (reply, tag); + if (!p) + return -1; + + /* Skip the tag */ + p += strlen (tag); + + /* Skip spaces */ + while (isspace (*p)) + p++; + + p = temp = g_strdup (p); + + /* Cut off the string after the dBm */ + while (isdigit (*p) || (*p == '-')) + p++; + *p = '\0'; + + /* When registered with EVDO, RX0/RX1 are returned by many cards with + * negative dBm. When registered only with 1x, some cards return "1x RSSI" + * with positive dBm. + */ + + if (mm_get_int_from_str (temp, &dbm)) { + if (*temp == '-') { + /* Some cards appear to use RX0/RX1 and output RSSI in negative dBm */ + if (dbm < 0) + success = TRUE; + } else if (isdigit (*temp) && (dbm > 0) && (dbm <= 125)) { + /* S720 appears to use "1x RSSI" and print RSSI in dBm without '-' */ + dbm *= -1; + success = TRUE; + } + } + + if (success) + quality = MM_RSSI_TO_QUALITY (dbm); + + g_free (temp); + return quality; +} + +static void +nwrssi_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + gint quality; + + response = mm_base_modem_at_command_finish (self, res, NULL); + if (!response) { + /* Fallback to parent's method */ + iface_modem_parent->load_signal_quality ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_signal_quality_ready, + task); + return; + } + + /* Parse the signal quality */ + quality = get_one_quality (response, "RX0="); + if (quality < 0) + quality = get_one_quality (response, "1x RSSI="); + if (quality < 0) + quality = get_one_quality (response, "RX1="); + if (quality < 0) + quality = get_one_quality (response, "HDR RSSI="); + + if (quality >= 0) + g_task_return_int (task, quality); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse $NWRSSI response: '%s'", + response); + g_object_unref (task); +} + +static void +modem_load_signal_quality (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* 3GPP modems can just run parent's signal quality loading */ + if (mm_iface_modem_is_3gpp (self)) { + iface_modem_parent->load_signal_quality ( + self, + (GAsyncReadyCallback)parent_load_signal_quality_ready, + task); + return; + } + + /* CDMA modems need custom signal quality loading */ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "$NWRSSI", + 3, + FALSE, + (GAsyncReadyCallback)nwrssi_ready, + task); +} + +/*****************************************************************************/ +/* Automatic activation (CDMA interface) */ + +static gboolean +modem_cdma_activate_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +qcmipgetp_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) + g_task_return_error (task, error); + else { + mm_obj_dbg (self, "current profile information retrieved: %s", response); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +activate_cdv_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Let's query the MIP profile */ + mm_base_modem_at_command (self, + "$QCMIPGETP", + 20, + FALSE, + (GAsyncReadyCallback)qcmipgetp_ready, + task); +} + +static void +modem_cdma_activate (MMIfaceModemCdma *self, + const gchar *carrier_code, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *cmd; + + task = g_task_new (self, NULL, callback, user_data); + + cmd = g_strdup_printf ("+CDV=%s", carrier_code); + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 20, + FALSE, + (GAsyncReadyCallback)activate_cdv_ready, + task); + g_free (cmd); +} + +/*****************************************************************************/ +/* Manual activation (CDMA interface) */ + +/* Wait up to 2 minutes */ +#define MAX_IOTA_QUERY_RETRIES 24 +#define MAX_IOTA_QUERY_RETRY_TIME 5 + +typedef enum { + CDMA_ACTIVATION_STEP_FIRST, + CDMA_ACTIVATION_STEP_REQUEST_ACTIVATION, + CDMA_ACTIVATION_STEP_OTA_UPDATE, + CDMA_ACTIVATION_STEP_PRL_UPDATE, + CDMA_ACTIVATION_STEP_WAIT_UNTIL_FINISHED, + CDMA_ACTIVATION_STEP_LAST +} CdmaActivationStep; + +typedef struct { + CdmaActivationStep step; + MMCdmaManualActivationProperties *properties; + guint wait_timeout_id; + guint wait_retries; +} CdmaActivationContext; + +static void +cdma_activation_context_free (CdmaActivationContext *ctx) +{ + g_assert (ctx->wait_timeout_id == 0); + g_object_unref (ctx->properties); + g_slice_free (CdmaActivationContext, ctx); +} + +static gboolean +modem_cdma_activate_manual_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void cdma_activation_step (GTask *task); + +static gboolean +cdma_activation_step_retry (GTask *task) +{ + CdmaActivationContext *ctx; + + ctx = g_task_get_task_data (task); + ctx->wait_timeout_id = 0; + cdma_activation_step (task); + return G_SOURCE_REMOVE; +} + +static void +iota_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + CdmaActivationContext *ctx; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + /* Finished? */ + if (strstr (response, "IOTA Enabled")) { + ctx->step++; + cdma_activation_step (task); + return; + } + + /* Too many retries? */ + if (ctx->wait_retries == MAX_IOTA_QUERY_RETRIES) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Too much time waiting to finish the IOTA activation"); + g_object_unref (task); + return; + } + + /* Otherwise, schedule retry in some secs */ + g_assert (ctx->wait_timeout_id == 0); + ctx->wait_retries++; + ctx->wait_timeout_id = g_timeout_add_seconds (MAX_IOTA_QUERY_RETRY_TIME, + (GSourceFunc)cdma_activation_step_retry, + task); +} + +static void +activation_command_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + CdmaActivationContext *ctx; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Keep on */ + ctx = g_task_get_task_data (task); + ctx->step++; + cdma_activation_step (task); +} + +static void +cdma_activation_step (GTask *task) +{ + MMBroadbandModemNovatel *self; + CdmaActivationContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case CDMA_ACTIVATION_STEP_FIRST: + mm_obj_dbg (self, "launching manual activation..."); + ctx->step++; + /* fall-through */ + + case CDMA_ACTIVATION_STEP_REQUEST_ACTIVATION: { + gchar *command; + + mm_obj_msg (self, "activation step [1/5]: setting up activation details"); + command = g_strdup_printf ("$NWACTIVATION=%s,%s,%s", + mm_cdma_manual_activation_properties_get_spc (ctx->properties), + mm_cdma_manual_activation_properties_get_mdn (ctx->properties), + mm_cdma_manual_activation_properties_get_min (ctx->properties)); + mm_base_modem_at_command (MM_BASE_MODEM (self), + command, + 20, + FALSE, + (GAsyncReadyCallback)activation_command_ready, + task); + g_free (command); + return; + } + + case CDMA_ACTIVATION_STEP_OTA_UPDATE: + mm_obj_msg (self, "activation step [2/5]: starting OTA activation"); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+IOTA=1", + 20, + FALSE, + (GAsyncReadyCallback)activation_command_ready, + task); + return; + + case CDMA_ACTIVATION_STEP_PRL_UPDATE: + mm_obj_msg (self, "activation step [3/5]: starting PRL update"); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+IOTA=2", + 20, + FALSE, + (GAsyncReadyCallback)activation_command_ready, + task); + return; + + case CDMA_ACTIVATION_STEP_WAIT_UNTIL_FINISHED: + mm_obj_msg (self, "activation step [4/5]: checking activation process status"); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+IOTA?", + 20, + FALSE, + (GAsyncReadyCallback)iota_query_ready, + task); + return; + + case CDMA_ACTIVATION_STEP_LAST: + mm_obj_msg (self, "activation step [5/5]: activation process finished"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +modem_cdma_activate_manual (MMIfaceModemCdma *self, + MMCdmaManualActivationProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + CdmaActivationContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + + /* Setup context */ + ctx = g_slice_new0 (CdmaActivationContext); + ctx->properties = g_object_ref (properties); + ctx->step = CDMA_ACTIVATION_STEP_FIRST; + g_task_set_task_data (task, ctx, (GDestroyNotify) cdma_activation_context_free); + + /* And start it */ + cdma_activation_step (task); +} + +/*****************************************************************************/ +/* Enable unsolicited events (SMS indications) (Messaging interface) */ + +static gboolean +messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +messaging_enable_unsolicited_events (MMIfaceModemMessaging *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Many Qualcomm chipsets don't support mode=2 */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CNMI=1,1,2,1,0", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Detailed registration state (CDMA interface) */ + +typedef struct { + MMModemCdmaRegistrationState cdma1x_state; + MMModemCdmaRegistrationState evdo_state; +} DetailedRegistrationStateResult; + +typedef struct { + MMPortSerialQcdm *port; + gboolean close_port; + MMModemCdmaRegistrationState cdma1x_state; + MMModemCdmaRegistrationState evdo_state; +} DetailedRegistrationStateContext; + +static void +detailed_registration_state_context_free (DetailedRegistrationStateContext *ctx) +{ + if (ctx->port) { + if (ctx->close_port) + mm_port_serial_close (MM_PORT_SERIAL (ctx->port)); + g_object_unref (ctx->port); + } + g_free (ctx); +} + +static gboolean +modem_cdma_get_detailed_registration_state_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + MMModemCdmaRegistrationState *detailed_cdma1x_state, + MMModemCdmaRegistrationState *detailed_evdo_state, + GError **error) +{ + GTask *task = G_TASK (res); + DetailedRegistrationStateContext *ctx = g_task_get_task_data (task);; + + if (!g_task_propagate_boolean (task, error)) + return FALSE; + + *detailed_cdma1x_state = ctx->cdma1x_state; + *detailed_evdo_state = ctx->evdo_state; + return TRUE; +} + +static void +parse_modem_eri (DetailedRegistrationStateContext *ctx, QcdmResult *result) +{ + MMModemCdmaRegistrationState new_state; + guint8 indicator_id = 0, icon_id = 0, icon_mode = 0; + + qcdm_result_get_u8 (result, QCDM_CMD_NW_SUBSYS_ERI_ITEM_INDICATOR_ID, &indicator_id); + qcdm_result_get_u8 (result, QCDM_CMD_NW_SUBSYS_ERI_ITEM_ICON_ID, &icon_id); + qcdm_result_get_u8 (result, QCDM_CMD_NW_SUBSYS_ERI_ITEM_ICON_MODE, &icon_mode); + + /* We use the "Icon ID" (also called the "Icon Index") because if it is 1, + * the device is never roaming. Any operator-defined IDs (greater than 2) + * may or may not be roaming, but that's operator-defined and we don't + * know anything about them. + * + * Indicator ID: + * 0 appears to be "not roaming", contrary to standard ERI values + * >= 1 appears to be the actual ERI value, which may or may not be + * roaming depending on the operator's custom ERI list + * + * Icon ID: + * 0 = roaming indicator on + * 1 = roaming indicator off + * 2 = roaming indicator flash + * + * Icon Mode: + * 0 = normal + * 1 = flash (only used with Icon ID >= 2) + * + * Roaming example: + * Roam: 160 + * Indicator ID: 160 + * Icon ID: 3 + * Icon Mode: 0 + * Call Prompt: 1 + * + * Home example: + * Roam: 0 + * Indicator ID: 0 + * Icon ID: 1 + * Icon Mode: 0 + * Call Prompt: 1 + */ + if (icon_id == 1) + new_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME; + else + new_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING; + + if (ctx->cdma1x_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) + ctx->cdma1x_state = new_state; + if (ctx->evdo_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) + ctx->evdo_state = new_state; +} + +static void +reg_eri_6500_cb (MMPortSerialQcdm *port, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemNovatel *self; + DetailedRegistrationStateContext *ctx; + GError *error = NULL; + GByteArray *response; + QcdmResult *result; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + response = mm_port_serial_qcdm_command_finish (port, res, &error); + if (error) { + /* Just ignore the error and complete with the input info */ + mm_obj_dbg (self, "couldn't run QCDM MSM6500 ERI: %s", error->message); + g_error_free (error); + goto done; + } + + result = qcdm_cmd_nw_subsys_eri_result ((const gchar *) response->data, response->len, NULL); + g_byte_array_unref (response); + if (result) { + parse_modem_eri (ctx, result); + qcdm_result_unref (result); + } + +done: + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +reg_eri_6800_cb (MMPortSerialQcdm *port, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemNovatel *self; + DetailedRegistrationStateContext *ctx; + GError *error = NULL; + GByteArray *response; + GByteArray *nweri; + QcdmResult *result; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + response = mm_port_serial_qcdm_command_finish (port, res, &error); + if (error) { + /* Just ignore the error and complete with the input info */ + mm_obj_dbg (self, "couldn't run QCDM MSM6800 ERI: %s", error->message); + g_error_free (error); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Parse the response */ + result = qcdm_cmd_nw_subsys_eri_result ((const gchar *) response->data, response->len, NULL); + g_byte_array_unref (response); + if (result) { + /* Success */ + parse_modem_eri (ctx, result); + qcdm_result_unref (result); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Try for MSM6500 */ + nweri = g_byte_array_sized_new (25); + nweri->len = qcdm_cmd_nw_subsys_eri_new ((char *) nweri->data, 25, QCDM_NW_CHIPSET_6500); + g_assert (nweri->len); + mm_port_serial_qcdm_command (port, + nweri, + 3, + NULL, + (GAsyncReadyCallback)reg_eri_6500_cb, + task); + g_byte_array_unref (nweri); +} + +static void +modem_cdma_get_detailed_registration_state (MMIfaceModemCdma *self, + MMModemCdmaRegistrationState cdma1x_state, + MMModemCdmaRegistrationState evdo_state, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DetailedRegistrationStateContext *ctx; + GTask *task; + GByteArray *nweri; + GError *error = NULL; + + /* Setup context */ + task = g_task_new (self, NULL, callback, user_data); + ctx = g_new0 (DetailedRegistrationStateContext, 1); + g_task_set_task_data (task, ctx, (GDestroyNotify) detailed_registration_state_context_free); + + ctx->cdma1x_state = cdma1x_state; + ctx->evdo_state = evdo_state; + + ctx->port = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self)); + if (!ctx->port) { + /* Ignore errors and use non-detailed registration state */ + mm_obj_dbg (self, "no available QCDM port"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->port), &error)) { + /* Ignore errors and use non-detailed registration state */ + mm_obj_dbg (self, "couldn't open QCDM port: %s", error->message); + g_error_free (error); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + ctx->close_port = TRUE; + + /* Try MSM6800 first since newer cards use that */ + nweri = g_byte_array_sized_new (25); + nweri->len = qcdm_cmd_nw_subsys_eri_new ((char *) nweri->data, 25, QCDM_NW_CHIPSET_6800); + g_assert (nweri->len); + mm_port_serial_qcdm_command (ctx->port, + nweri, + 3, + NULL, + (GAsyncReadyCallback)reg_eri_6800_cb, + task); + g_byte_array_unref (nweri); +} + +/*****************************************************************************/ +/* Load network time (Time interface) */ + +static gboolean +parse_nwltime_reply (const char *response, + gchar **out_iso_8601, + MMNetworkTimezone **out_tz, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *match_error = NULL; + guint year; + guint month; + guint day; + guint hour; + guint minute; + guint second; + g_autofree gchar *result = NULL; + gint utc_offset = 0; + gboolean success = FALSE; + + /* Sample reply: 2013.3.27.15.47.19.2.-5 */ + r = g_regex_new ("(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.([\\-\\+\\d]+)$", 0, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse $NWLTIME results: "); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match $NWLTIME reply"); + } + } else { + /* Remember that g_match_info_get_match_count() includes match #0 */ + g_assert (g_match_info_get_match_count (match_info) >= 9); + + if (mm_get_uint_from_match_info (match_info, 1, &year) && + mm_get_uint_from_match_info (match_info, 2, &month) && + mm_get_uint_from_match_info (match_info, 3, &day) && + mm_get_uint_from_match_info (match_info, 4, &hour) && + mm_get_uint_from_match_info (match_info, 5, &minute) && + mm_get_uint_from_match_info (match_info, 6, &second) && + mm_get_int_from_match_info (match_info, 8, &utc_offset)) { + result = mm_new_iso8601_time (year, month, day, hour, minute, second, + TRUE, utc_offset * 60, error); + if (out_tz) { + *out_tz = mm_network_timezone_new (); + mm_network_timezone_set_offset (*out_tz, utc_offset * 60); + } + + success = (result != NULL); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse $NWLTIME reply"); + } + } + + if (out_iso_8601) + *out_iso_8601 = g_steal_pointer (&result); + + return success; +} + +static gchar * +modem_time_load_network_time_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + const gchar *response; + gchar *result = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (response) + parse_nwltime_reply (response, &result, NULL, error); + return result; +} + +static void +modem_time_load_network_time (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "$NWLTIME", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load network timezone (Time interface) */ + +static MMNetworkTimezone * +modem_time_load_network_timezone_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + const gchar *response; + MMNetworkTimezone *tz = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (response) + parse_nwltime_reply (response, NULL, &tz, error); + return tz; +} + +static void +modem_time_load_network_timezone (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "$NWLTIME", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Check support (Time interface) */ + +static gboolean +modem_time_check_support_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_time_check_support (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Only CDMA devices support this at the moment */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "$NWLTIME", + 3, + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ + +MMBroadbandModemNovatel * +mm_broadband_modem_novatel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_NOVATEL, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_novatel_init (MMBroadbandModemNovatel *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->load_access_technologies_finish = modem_load_access_technologies_finish; + iface->load_access_technologies = modem_load_access_technologies; + iface->load_signal_quality = modem_load_signal_quality; + iface->load_signal_quality_finish = modem_load_signal_quality_finish; +} + +static void +iface_modem_messaging_init (MMIfaceModemMessaging *iface) +{ + iface->enable_unsolicited_events = messaging_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = messaging_enable_unsolicited_events_finish; +} + +static void +iface_modem_cdma_init (MMIfaceModemCdma *iface) +{ + iface->get_detailed_registration_state = modem_cdma_get_detailed_registration_state; + iface->get_detailed_registration_state_finish = modem_cdma_get_detailed_registration_state_finish; + iface->activate = modem_cdma_activate; + iface->activate_finish = modem_cdma_activate_finish; + iface->activate_manual = modem_cdma_activate_manual; + iface->activate_manual_finish = modem_cdma_activate_manual_finish; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface->check_support = modem_time_check_support; + iface->check_support_finish = modem_time_check_support_finish; + iface->load_network_time = modem_time_load_network_time; + iface->load_network_time_finish = modem_time_load_network_time_finish; + iface->load_network_timezone = modem_time_load_network_timezone; + iface->load_network_timezone_finish = modem_time_load_network_timezone_finish; +} + +static void +mm_broadband_modem_novatel_class_init (MMBroadbandModemNovatelClass *klass) +{ +} diff --git a/src/plugins/novatel/mm-broadband-modem-novatel.h b/src/plugins/novatel/mm-broadband-modem-novatel.h new file mode 100644 index 00000000..36604172 --- /dev/null +++ b/src/plugins/novatel/mm-broadband-modem-novatel.h @@ -0,0 +1,51 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_NOVATEL_H +#define MM_BROADBAND_MODEM_NOVATEL_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_NOVATEL (mm_broadband_modem_novatel_get_type ()) +#define MM_BROADBAND_MODEM_NOVATEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL, MMBroadbandModemNovatel)) +#define MM_BROADBAND_MODEM_NOVATEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_NOVATEL, MMBroadbandModemNovatelClass)) +#define MM_IS_BROADBAND_MODEM_NOVATEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL)) +#define MM_IS_BROADBAND_MODEM_NOVATEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_NOVATEL)) +#define MM_BROADBAND_MODEM_NOVATEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL, MMBroadbandModemNovatelClass)) + +typedef struct _MMBroadbandModemNovatel MMBroadbandModemNovatel; +typedef struct _MMBroadbandModemNovatelClass MMBroadbandModemNovatelClass; +typedef struct _MMBroadbandModemNovatelPrivate MMBroadbandModemNovatelPrivate; + +struct _MMBroadbandModemNovatel { + MMBroadbandModem parent; + MMBroadbandModemNovatelPrivate *priv; +}; + +struct _MMBroadbandModemNovatelClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_novatel_get_type (void); + +MMBroadbandModemNovatel *mm_broadband_modem_novatel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_NOVATEL_H */ diff --git a/src/plugins/novatel/mm-common-novatel.c b/src/plugins/novatel/mm-common-novatel.c new file mode 100644 index 00000000..b6b0e272 --- /dev/null +++ b/src/plugins/novatel/mm-common-novatel.c @@ -0,0 +1,145 @@ +/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-common-novatel.h" +#include "mm-log-object.h" + +/*****************************************************************************/ +/* Custom init */ + +typedef struct { + MMPortSerialAt *port; + guint nwdmat_retries; + guint wait_time; +} CustomInitContext; + +static void +custom_init_context_free (CustomInitContext *ctx) +{ + g_object_unref (ctx->port); + g_slice_free (CustomInitContext, ctx); +} + +gboolean +mm_common_novatel_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void custom_init_step (GTask *task); + +static void +nwdmat_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + MMPortProbe *probe; + + probe = g_task_get_source_object (task); + + mm_port_serial_at_command_finish (port, res, &error); + if (error) { + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { + custom_init_step (task); + return; + } + + mm_obj_dbg (probe, "error flipping secondary ports to AT mode: %s", error->message); + } + + /* Finish custom_init */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static gboolean +custom_init_wait_cb (GTask *task) +{ + custom_init_step (task); + return G_SOURCE_REMOVE; +} + +static void +custom_init_step (GTask *task) +{ + CustomInitContext *ctx; + MMPortProbe *probe; + + probe = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* If cancelled, end */ + if (g_task_return_error_if_cancelled (task)) { + mm_obj_dbg (probe, "no need to keep on running custom init"); + g_object_unref (task); + return; + } + + /* If device has a QMI port, don't run $NWDMAT */ + if (mm_port_probe_list_has_qmi_port (mm_device_peek_port_probe_list (mm_port_probe_peek_device (probe)))) { + mm_obj_dbg (probe, "no need to run custom init: device has QMI port"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (ctx->wait_time > 0) { + ctx->wait_time--; + g_timeout_add_seconds (1, (GSourceFunc)custom_init_wait_cb, task); + return; + } + + if (ctx->nwdmat_retries > 0) { + ctx->nwdmat_retries--; + mm_port_serial_at_command (ctx->port, + "$NWDMAT=1", + 3, + FALSE, /* raw */ + FALSE, /* allow_cached */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)nwdmat_ready, + task); + return; + } + + /* Finish custom_init */ + mm_obj_dbg (probe, "couldn't flip secondary port to AT: all retries consumed"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_common_novatel_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CustomInitContext *ctx; + GTask *task; + + ctx = g_slice_new (CustomInitContext); + ctx->port = g_object_ref (port); + ctx->nwdmat_retries = 3; + ctx->wait_time = 2; + + task = g_task_new (probe, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)custom_init_context_free); + + custom_init_step (task); +} diff --git a/src/plugins/novatel/mm-common-novatel.h b/src/plugins/novatel/mm-common-novatel.h new file mode 100644 index 00000000..70572fd3 --- /dev/null +++ b/src/plugins/novatel/mm-common-novatel.h @@ -0,0 +1,31 @@ +/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_COMMON_NOVATEL_H +#define MM_COMMON_NOVATEL_H + +#include "glib.h" +#include "mm-plugin.h" + +void mm_common_novatel_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_common_novatel_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error); + +#endif /* MM_COMMON_NOVATEL_H */ diff --git a/src/plugins/novatel/mm-plugin-novatel-lte.c b/src/plugins/novatel/mm-plugin-novatel-lte.c new file mode 100644 index 00000000..025707ae --- /dev/null +++ b/src/plugins/novatel/mm-plugin-novatel-lte.c @@ -0,0 +1,81 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2012 Google Inc. + * Author: Nathan Williams <njw@google.com> + */ + +#include <string.h> +#include <gmodule.h> + +#include "mm-plugin-novatel-lte.h" +#include "mm-private-boxed-types.h" +#include "mm-broadband-modem-novatel-lte.h" + +G_DEFINE_TYPE (MMPluginNovatelLte, mm_plugin_novatel_lte, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_novatel_lte_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", NULL }; + static const mm_uint16_pair products[] = { { 0x1410, 0x9010 }, /* Novatel E362 */ + {0, 0} }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_NOVATEL_LTE, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_PRODUCT_IDS, products, + MM_PLUGIN_ALLOWED_SINGLE_AT, TRUE, + NULL)); +} + +static void +mm_plugin_novatel_lte_init (MMPluginNovatelLte *self) +{ +} + +static void +mm_plugin_novatel_lte_class_init (MMPluginNovatelLteClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/novatel/mm-plugin-novatel-lte.h b/src/plugins/novatel/mm-plugin-novatel-lte.h new file mode 100644 index 00000000..8f6c8be1 --- /dev/null +++ b/src/plugins/novatel/mm-plugin-novatel-lte.h @@ -0,0 +1,47 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2012 Google Inc. + * Author: Nathan Williams <njw@google.com> + */ + +#ifndef MM_PLUGIN_NOVATEL_LTE_H +#define MM_PLUGIN_NOVATEL_LTE_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_NOVATEL_LTE (mm_plugin_novatel_lte_get_type ()) +#define MM_PLUGIN_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_NOVATEL_LTE, MMPluginNovatelLte)) +#define MM_PLUGIN_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_NOVATEL_LTE, MMPluginNovatelLteClass)) +#define MM_IS_PLUGIN_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_NOVATEL_LTE)) +#define MM_IS_PLUGIN_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_NOVATEL_LTE)) +#define MM_PLUGIN_NOVATEL_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_NOVATEL_LTE, MMPluginNovatelLteClass)) + +typedef struct { + MMPlugin parent; +} MMPluginNovatelLte; + +typedef struct { + MMPluginClass parent; +} MMPluginNovatelLteClass; + +GType mm_plugin_novatel_lte_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_NOVATEL_LTE_H */ diff --git a/src/plugins/novatel/mm-plugin-novatel.c b/src/plugins/novatel/mm-plugin-novatel.c new file mode 100644 index 00000000..c17f6a9a --- /dev/null +++ b/src/plugins/novatel/mm-plugin-novatel.c @@ -0,0 +1,113 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-novatel.h" +#include "mm-common-novatel.h" +#include "mm-private-boxed-types.h" +#include "mm-broadband-modem-novatel.h" +#include "mm-log-object.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi.h" +#endif + +G_DEFINE_TYPE (MMPluginNovatel, mm_plugin_novatel, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Novatel modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_novatel_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendors[] = { 0x1410, 0 }; + static const mm_uint16_pair forbidden_products[] = { { 0x1410, 0x9010 }, /* Novatel E362 */ + { 0, 0 } }; + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (mm_common_novatel_custom_init), + .finish = G_CALLBACK (mm_common_novatel_custom_init_finish), + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_NOVATEL, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendors, + MM_PLUGIN_FORBIDDEN_PRODUCT_IDS, forbidden_products, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + MM_PLUGIN_REQUIRED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + NULL)); +} + +static void +mm_plugin_novatel_init (MMPluginNovatel *self) +{ +} + +static void +mm_plugin_novatel_class_init (MMPluginNovatelClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/novatel/mm-plugin-novatel.h b/src/plugins/novatel/mm-plugin-novatel.h new file mode 100644 index 00000000..b553e278 --- /dev/null +++ b/src/plugins/novatel/mm-plugin-novatel.h @@ -0,0 +1,48 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_NOVATEL_H +#define MM_PLUGIN_NOVATEL_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_NOVATEL (mm_plugin_novatel_get_type ()) +#define MM_PLUGIN_NOVATEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_NOVATEL, MMPluginNovatel)) +#define MM_PLUGIN_NOVATEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_NOVATEL, MMPluginNovatelClass)) +#define MM_IS_PLUGIN_NOVATEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_NOVATEL)) +#define MM_IS_PLUGIN_NOVATEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_NOVATEL)) +#define MM_PLUGIN_NOVATEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_NOVATEL, MMPluginNovatelClass)) + +typedef struct { + MMPlugin parent; +} MMPluginNovatel; + +typedef struct { + MMPluginClass parent; +} MMPluginNovatelClass; + +GType mm_plugin_novatel_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_NOVATEL_H */ diff --git a/src/plugins/novatel/mm-shared.c b/src/plugins/novatel/mm-shared.c new file mode 100644 index 00000000..6bd11e4b --- /dev/null +++ b/src/plugins/novatel/mm-shared.c @@ -0,0 +1,20 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-shared.h" + +MM_SHARED_DEFINE_MAJOR_VERSION +MM_SHARED_DEFINE_MINOR_VERSION +MM_SHARED_DEFINE_NAME(Novatel) diff --git a/src/plugins/novatel/mm-sim-novatel-lte.c b/src/plugins/novatel/mm-sim-novatel-lte.c new file mode 100644 index 00000000..4d71bd80 --- /dev/null +++ b/src/plugins/novatel/mm-sim-novatel-lte.c @@ -0,0 +1,234 @@ +/* -*- 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) 2012 Google, Inc. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> +#include "mm-modem-helpers.h" +#include "mm-base-modem-at.h" + +#include "mm-sim-novatel-lte.h" + +G_DEFINE_TYPE (MMSimNovatelLte, mm_sim_novatel_lte, MM_TYPE_BASE_SIM) + +/*****************************************************************************/ +/* IMSI loading */ + +static gchar * +load_imsi_finish (MMBaseSim *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +imsi_read_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response, *str; + gchar buf[19]; + gchar imsi[16]; + gsize len = 0; + gint sw1, sw2; + gint i; + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + memset (buf, 0, sizeof (buf)); + str = mm_strip_tag (response, "+CRSM:"); + + /* With or without quotes... */ + if (sscanf (str, "%d,%d,\"%18c\"", &sw1, &sw2, (char *) &buf) != 3 && + sscanf (str, "%d,%d,%18c", &sw1, &sw2, (char *) &buf) != 3) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse the CRSM response: '%s'", + response); + g_object_unref (task); + return; + } + + if ((sw1 != 0x90 || sw2 != 0x00) && + (sw1 != 0x91) && + (sw1 != 0x92) && + (sw1 != 0x9f)) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "SIM failed to handle CRSM request (sw1 %d sw2 %d)", + sw1, sw2); + g_object_unref (task); + return; + } + + /* Make sure the buffer is only digits or 'F' */ + for (len = 0; len < sizeof (buf) && buf[len]; len++) { + if (isdigit (buf[len])) + continue; + if (buf[len] == 'F' || buf[len] == 'f') { + buf[len] = 'F'; /* canonicalize the F */ + continue; + } + if (buf[len] == '\"') { + buf[len] = 0; + break; + } + + /* Invalid character */ + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "CRSM IMSI response contained invalid character '%c'", + buf[len]); + g_object_unref (task); + return; + } + + /* BCD encoded IMSIs plus the length byte and parity are 18 digits long */ + if (len != 18) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid +CRSM IMSI response size (was %zd, expected 18)", + len); + g_object_unref (task); + return; + } + + /* Skip the length byte (digit 0-1) and parity (digit 3). Swap digits in + * the EFimsi response to get the actual IMSI, each group of 2 digits is + * reversed in the +CRSM response. i.e.: + * + * **0*21436587a9cbed -> 0123456789abcde + */ + memset (imsi, 0, sizeof (imsi)); + imsi[0] = buf[2]; + for (i = 1; i < 8; i++) { + imsi[(i * 2) - 1] = buf[(i * 2) + 3]; + imsi[i * 2] = buf[(i * 2) + 2]; + } + + /* Zero out the first F, if any, for IMSIs shorter than 15 digits */ + for (i = 0; i < 15; i++) { + if (imsi[i] == 'F') { + imsi[i++] = 0; + break; + } + } + + /* Ensure all 'F's, if any, are at the end */ + for (; i < 15; i++) { + if (imsi[i] != 'F') { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid +CRSM IMSI length (unexpected F)"); + g_object_unref (task); + return; + } + } + + g_task_return_pointer (task, g_strdup (imsi), g_free); + g_object_unref (task); +} + +static void +load_imsi (MMBaseSim *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBaseModem *modem = NULL; + + g_object_get (self, + MM_BASE_SIM_MODEM, &modem, + NULL); + + mm_base_modem_at_command ( + modem, + "+CRSM=176,28423,0,0,9", + 3, + FALSE, + (GAsyncReadyCallback)imsi_read_ready, + g_task_new (self, NULL, callback, user_data)); + g_object_unref (modem); +} + +/*****************************************************************************/ + +MMBaseSim * +mm_sim_novatel_lte_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *sim; + + source = g_async_result_get_source_object (res); + sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!sim) + return NULL; + + /* Only export valid SIMs */ + mm_base_sim_export (MM_BASE_SIM (sim)); + + return MM_BASE_SIM (sim); +} + +void +mm_sim_novatel_lte_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (MM_TYPE_SIM_NOVATEL_LTE, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_SIM_MODEM, modem, + "active", TRUE, /* by default always active */ + NULL); +} + +static void +mm_sim_novatel_lte_init (MMSimNovatelLte *self) +{ +} + +static void +mm_sim_novatel_lte_class_init (MMSimNovatelLteClass *klass) +{ + MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass); + + base_sim_class->load_imsi = load_imsi; + base_sim_class->load_imsi_finish = load_imsi_finish; +} diff --git a/src/plugins/novatel/mm-sim-novatel-lte.h b/src/plugins/novatel/mm-sim-novatel-lte.h new file mode 100644 index 00000000..df8f38e6 --- /dev/null +++ b/src/plugins/novatel/mm-sim-novatel-lte.h @@ -0,0 +1,51 @@ +/* -*- 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) 2012 Google, Inc. + */ + +#ifndef MM_SIM_NOVATEL_LTE_H +#define MM_SIM_NOVATEL_LTE_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-sim.h" + +#define MM_TYPE_SIM_NOVATEL_LTE (mm_sim_novatel_lte_get_type ()) +#define MM_SIM_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_NOVATEL_LTE, MMSimNovatelLte)) +#define MM_SIM_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_NOVATEL_LTE, MMSimNovatelLteClass)) +#define MM_IS_SIM_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_NOVATEL_LTE)) +#define MM_IS_SIM_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_NOVATEL_LTE)) +#define MM_SIM_NOVATEL_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_NOVATEL_LTE, MMSimNovatelLteClass)) + +typedef struct _MMSimNovatelLte MMSimNovatelLte; +typedef struct _MMSimNovatelLteClass MMSimNovatelLteClass; + +struct _MMSimNovatelLte { + MMBaseSim parent; +}; + +struct _MMSimNovatelLteClass { + MMBaseSimClass parent; +}; + +GType mm_sim_novatel_lte_get_type (void); + +void mm_sim_novatel_lte_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_sim_novatel_lte_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_SIM_NOVATEL_LTE_H */ diff --git a/src/plugins/option/mm-broadband-bearer-hso.c b/src/plugins/option/mm-broadband-bearer-hso.c new file mode 100644 index 00000000..9199e7b3 --- /dev/null +++ b/src/plugins/option/mm-broadband-bearer-hso.c @@ -0,0 +1,818 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-hso.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-daemon-enums-types.h" + +G_DEFINE_TYPE (MMBroadbandBearerHso, mm_broadband_bearer_hso, MM_TYPE_BROADBAND_BEARER); + +struct _MMBroadbandBearerHsoPrivate { + guint auth_idx; + + GTask *connect_pending; + guint connect_pending_id; + gulong connect_port_closed_id; +}; + +/*****************************************************************************/ +/* 3GPP IP config retrieval (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + guint cid; +} GetIpConfig3gppContext; + +static void +get_ip_config_context_free (GetIpConfig3gppContext *ctx) +{ + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_slice_free (GetIpConfig3gppContext, ctx); +} + +static gboolean +get_ip_config_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + MMBearerIpConfig **ipv4_config, + MMBearerIpConfig **ipv6_config, + GError **error) +{ + MMBearerIpConfig *ip_config; + + ip_config = g_task_propagate_pointer (G_TASK (res), error); + if (!ip_config) + return FALSE; + + /* No IPv6 for now */ + *ipv4_config = ip_config; /* Transfer ownership */ + *ipv6_config = NULL; + return TRUE; +} + +#define OWANDATA_TAG "_OWANDATA: " + +static void +ip_config_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GetIpConfig3gppContext *ctx; + MMBearerIpConfig *ip_config = NULL; + const gchar *response; + GError *error = NULL; + gchar **items; + gchar *dns[3] = { 0 }; + guint i; + guint dns_i; + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* TODO: use a regex to parse this */ + + /* Check result */ + if (!g_str_has_prefix (response, OWANDATA_TAG)) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get IP config: invalid response '%s'", + response); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + response = mm_strip_tag (response, OWANDATA_TAG); + items = g_strsplit (response, ", ", 0); + + for (i = 0, dns_i = 0; items[i]; i++) { + if (i == 0) { /* CID */ + guint num; + + if (!mm_get_uint_from_str (items[i], &num) || + num != ctx->cid) { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unknown CID in OWANDATA response (" + "got %d, expected %d)", (guint) num, ctx->cid); + break; + } + } else if (i == 1) { /* IP address */ + guint32 tmp; + + if (!inet_pton (AF_INET, items[i], &tmp)) + break; + + ip_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ip_config, MM_BEARER_IP_METHOD_STATIC); + mm_bearer_ip_config_set_address (ip_config, items[i]); + mm_bearer_ip_config_set_prefix (ip_config, 32); + } else if (i == 3 || i == 4) { /* DNS entries */ + guint32 tmp; + + if (!inet_pton (AF_INET, items[i], &tmp)) { + g_clear_object (&ip_config); + break; + } + + dns[dns_i++] = items[i]; + } + } + + if (!ip_config) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get IP config: couldn't parse response '%s'", + response); + } else { + /* If we got DNS entries, set them in the IP config */ + if (dns[0]) + mm_bearer_ip_config_set_dns (ip_config, (const gchar **)dns); + + g_task_return_pointer (task, ip_config, g_object_unref); + } + + g_object_unref (task); + g_strfreev (items); +} + +static void +get_ip_config_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + MMBearerIpFamily ip_family, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GetIpConfig3gppContext *ctx; + GTask *task; + gchar *command; + + ctx = g_slice_new0 (GetIpConfig3gppContext); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)get_ip_config_context_free); + + command = g_strdup_printf ("AT_OWANDATA=%d", cid); + mm_base_modem_at_command_full ( + MM_BASE_MODEM (modem), + primary, + command, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)ip_config_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + guint cid; + MMPort *data; + guint auth_idx; + GError *saved_error; +} Dial3gppContext; + +static void +dial_3gpp_context_free (Dial3gppContext *ctx) +{ + g_assert (!ctx->saved_error); + g_clear_object (&ctx->data); + g_clear_object (&ctx->primary); + g_clear_object (&ctx->modem); + g_slice_free (Dial3gppContext, ctx); +} + +static guint +dial_3gpp_get_connecting_cid (GTask *task) +{ + Dial3gppContext *ctx; + + ctx = g_task_get_task_data (task); + return ctx->cid; +} + +static MMPort * +dial_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return MM_PORT (g_task_propagate_pointer (G_TASK (res), error)); +} + +static void +connect_reset_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + Dial3gppContext *ctx; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_full_finish (modem, res, NULL); + + /* When reset is requested, it was either cancelled or an error was stored */ + if (!g_task_return_error_if_cancelled (task)) { + g_assert (ctx->saved_error); + g_task_return_error (task, ctx->saved_error); + ctx->saved_error = NULL; + } + + g_object_unref (task); +} + +static void +connect_reset (GTask *task) +{ + Dial3gppContext *ctx; + gchar *command; + + ctx = g_task_get_task_data (task); + + /* Need to reset the connection attempt */ + command = g_strdup_printf ("AT_OWANCALL=%d,0,1", ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)connect_reset_ready, + task); + g_free (command); +} + +static void +process_pending_connect_attempt (MMBroadbandBearerHso *self, + MMBearerConnectionStatus status) +{ + GTask *task; + Dial3gppContext *ctx; + + /* Recover task and remove both cancellation and timeout (if any)*/ + g_assert (self->priv->connect_pending); + task = self->priv->connect_pending; + self->priv->connect_pending = NULL; + + ctx = g_task_get_task_data (task); + + if (self->priv->connect_pending_id) { + g_source_remove (self->priv->connect_pending_id); + self->priv->connect_pending_id = 0; + } + + if (self->priv->connect_port_closed_id) { + g_signal_handler_disconnect (ctx->primary, self->priv->connect_port_closed_id); + self->priv->connect_port_closed_id = 0; + } + + /* Reporting connected */ + if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED) { + /* If we wanted to get cancelled before, do it now. */ + if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) { + connect_reset (task); + return; + } + + g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); + g_object_unref (task); + return; + } + + /* Received CONNECTION_FAILED or DISCONNECTED during a connection attempt, + * so return a failed error. Note that if the cancellable has been cancelled + * already, a cancelled error would be returned instead. */ + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Call setup failed"); + g_object_unref (task); +} + +static gboolean +connect_timed_out_cb (MMBroadbandBearerHso *self) +{ + GTask *task; + Dial3gppContext *ctx; + + /* Cleanup timeout ID */ + self->priv->connect_pending_id = 0; + + /* Recover task and own it */ + task = self->priv->connect_pending; + self->priv->connect_pending = NULL; + g_assert (task); + + ctx = g_task_get_task_data (task); + + /* Remove closed port watch, if found */ + if (self->priv->connect_port_closed_id) { + g_signal_handler_disconnect (ctx->primary, self->priv->connect_port_closed_id); + self->priv->connect_port_closed_id = 0; + } + + /* Setup error to return after the reset */ + g_assert (!ctx->saved_error); + ctx->saved_error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, + "Connection attempt timed out"); + + /* It's probably pointless to try to reset this here, but anyway... */ + connect_reset (task); + + return G_SOURCE_REMOVE; +} + +static void +forced_close_cb (MMBroadbandBearerHso *self) +{ + /* Just treat the forced close event as any other unsolicited message */ + mm_base_bearer_report_connection_status (MM_BASE_BEARER (self), + MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED); +} + +static void +activate_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerHso *self) +{ + GTask *task; + Dial3gppContext *ctx; + GError *error = NULL; + + task = g_steal_pointer (&self->priv->connect_pending); + + /* Try to recover the connection task. If none found, it means the + * task was already completed and we have nothing else to do. + * But note that we won't take owneship of the task yet! */ + if (!task) { + mm_obj_dbg (self, "connection context was finished already by an unsolicited message"); + /* Run _finish() to finalize the async call, even if we don't care + * about the result */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + goto out; + } + + /* From now on, if we get cancelled, we'll need to run the connection + * reset ourselves just in case */ + + /* Errors on the dial command are fatal */ + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + goto out; + } + + /* Track the task again */ + self->priv->connect_pending = task; + + /* We will now setup a timeout and keep the context in the bearer's private. + * Reports of modem being connected will arrive via unsolicited messages. + * This timeout should be long enough. Actually... ideally should never get + * reached. */ + self->priv->connect_pending_id = g_timeout_add_seconds (MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + (GSourceFunc)connect_timed_out_cb, + self); + + /* If we get the port closed, we treat as a connect error */ + ctx = g_task_get_task_data (task); + self->priv->connect_port_closed_id = g_signal_connect_swapped (ctx->primary, + "forced-close", + G_CALLBACK (forced_close_cb), + self); + + out: + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); +} + +static void authenticate (GTask *task); + +static void +authenticate_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerHso *self; + Dial3gppContext *ctx; + gchar *command; + + /* If cancelled, complete */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_full_finish (modem, res, NULL)) { + /* Try the next auth command */ + ctx->auth_idx++; + authenticate (task); + return; + } + + /* Store which auth command worked, for next attempts */ + self->priv->auth_idx = ctx->auth_idx; + + /* The unsolicited response to AT_OWANCALL may come before the OK does. + * We will keep the connection context in the bearer private data so + * that it is accessible from the unsolicited message handler. Note + * also that we do NOT pass the ctx to the GAsyncReadyCallback, as it + * may not be valid any more when the callback is called (it may be + * already completed in the unsolicited handling) */ + g_assert (self->priv->connect_pending == NULL); + self->priv->connect_pending = task; + + /* Success, activate the PDP context and start the data session */ + command = g_strdup_printf ("AT_OWANCALL=%d,1,1", ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback) activate_ready, + g_object_ref (self)); /* we pass the bearer object! */ + g_free (command); +} + +const gchar *auth_commands[] = { + "$QCPDPP", + /* Icera-based devices (GI0322/Quicksilver, iCON 505) don't implement + * $QCPDPP, but instead use _OPDPP with the same arguments. + */ + "_OPDPP", + NULL +}; + +static void +authenticate (GTask *task) +{ + MMBroadbandBearerHso *self; + Dial3gppContext *ctx; + gchar *command; + const gchar *user; + const gchar *password; + MMBearerAllowedAuth allowed_auth; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + if (!auth_commands[ctx->auth_idx]) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't run HSO authentication"); + g_object_unref (task); + return; + } + + user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + allowed_auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + + /* Both user and password are required; otherwise firmware returns an error */ + if (!user || !password || allowed_auth == MM_BEARER_ALLOWED_AUTH_NONE) { + mm_obj_dbg (self, "not using authentication"); + command = g_strdup_printf ("%s=%d,0", + auth_commands[ctx->auth_idx], + ctx->cid); + } else { + gchar *quoted_user; + gchar *quoted_password; + guint hso_auth; + + if (allowed_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN) { + mm_obj_dbg (self, "using default (CHAP) authentication method"); + hso_auth = 2; + } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_CHAP) { + mm_obj_dbg (self, "using CHAP authentication method"); + hso_auth = 2; + } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_PAP) { + mm_obj_dbg (self, "using PAP authentication method"); + hso_auth = 1; + } else { + gchar *str; + + str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot use any of the specified authentication methods (%s)", + str); + g_object_unref (task); + g_free (str); + return; + } + + quoted_user = mm_port_serial_at_quote_string (user); + quoted_password = mm_port_serial_at_quote_string (password); + command = g_strdup_printf ("%s=%d,%u,%s,%s", + auth_commands[ctx->auth_idx], + ctx->cid, + hso_auth, + quoted_password, + quoted_user); + g_free (quoted_user); + g_free (quoted_password); + } + + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)authenticate_ready, + task); + g_free (command); +} + +static void +dial_3gpp (MMBroadbandBearer *_self, + MMBaseModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerHso *self = MM_BROADBAND_BEARER_HSO (_self); + GTask *task; + Dial3gppContext *ctx; + + g_assert (primary != NULL); + + task = g_task_new (self, cancellable, callback, user_data); + + ctx = g_slice_new0 (Dial3gppContext); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + g_task_set_task_data (task, ctx, (GDestroyNotify)dial_3gpp_context_free); + + /* Always start with the index that worked last time + * (will be 0 the first time)*/ + ctx->auth_idx = self->priv->auth_idx; + + /* We need a net data port */ + ctx->data = mm_base_modem_get_best_data_port (modem, MM_PORT_TYPE_NET); + if (!ctx->data) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to launch connection"); + g_object_unref (task); + return; + } + + authenticate (task); +} + +/*****************************************************************************/ +/* 3GPP disconnect */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; +} DisconnectContext; + +static void +disconnect_context_free (DisconnectContext *ctx) +{ + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_free (ctx); +} + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +disconnect_owancall_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerHso *self; + GError *error = NULL; + + self = g_task_get_source_object (task); + + /* Ignore errors for now */ + mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + mm_obj_dbg (self, "disconnection failed (not fatal): %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +disconnect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + gchar *command; + DisconnectContext *ctx; + GTask *task; + + g_assert (primary != NULL); + + ctx = g_new0 (DisconnectContext, 1); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)disconnect_context_free); + + /* Use specific CID */ + command = g_strdup_printf ("AT_OWANCALL=%d,0,0", cid); + mm_base_modem_at_command_full (MM_BASE_MODEM (modem), + primary, + command, + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)disconnect_owancall_ready, + task); + g_free (command); +} + +/*****************************************************************************/ + +gint +mm_broadband_bearer_hso_get_connecting_profile_id (MMBroadbandBearerHso *self) +{ + return (self->priv->connect_pending ? + (gint)dial_3gpp_get_connecting_cid (self->priv->connect_pending) : + MM_3GPP_PROFILE_ID_UNKNOWN); +} + +/*****************************************************************************/ + +static void +report_connection_status (MMBaseBearer *_self, + MMBearerConnectionStatus status, + const GError *connection_error) +{ + MMBroadbandBearerHso *self = MM_BROADBAND_BEARER_HSO (_self); + + g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED || + status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED || + status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED); + + /* Process pending connection attempt */ + if (self->priv->connect_pending) { + process_pending_connect_attempt (self, status); + return; + } + + mm_obj_dbg (self, "received spontaneous _OWANCALL (%s)", + mm_bearer_connection_status_get_string (status)); + + if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) { + /* If no connection attempt on-going, make sure we mark ourselves as + * disconnected */ + MM_BASE_BEARER_CLASS (mm_broadband_bearer_hso_parent_class)->report_connection_status (_self, status,connection_error); + } +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_broadband_bearer_hso_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *bearer; + GObject *source; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_hso_new (MMBroadbandModemHso *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_HSO, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + NULL); +} + +static void +mm_broadband_bearer_hso_init (MMBroadbandBearerHso *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_BEARER_HSO, + MMBroadbandBearerHsoPrivate); +} + +static void +mm_broadband_bearer_hso_class_init (MMBroadbandBearerHsoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandBearerHsoPrivate)); + + base_bearer_class->report_connection_status = report_connection_status; + base_bearer_class->load_connection_status = NULL; + base_bearer_class->load_connection_status_finish = NULL; +#if defined WITH_SUSPEND_RESUME + base_bearer_class->reload_connection_status = NULL; + base_bearer_class->reload_connection_status_finish = NULL; +#endif + + broadband_bearer_class->dial_3gpp = dial_3gpp; + broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; + broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp; + broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; +} diff --git a/src/plugins/option/mm-broadband-bearer-hso.h b/src/plugins/option/mm-broadband-bearer-hso.h new file mode 100644 index 00000000..def46ac3 --- /dev/null +++ b/src/plugins/option/mm-broadband-bearer-hso.h @@ -0,0 +1,61 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_BEARER_HSO_H +#define MM_BROADBAND_BEARER_HSO_H + +#include <glib.h> +#include <glib-object.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-hso.h" + +#define MM_TYPE_BROADBAND_BEARER_HSO (mm_broadband_bearer_hso_get_type ()) +#define MM_BROADBAND_BEARER_HSO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_HSO, MMBroadbandBearerHso)) +#define MM_BROADBAND_BEARER_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_HSO, MMBroadbandBearerHsoClass)) +#define MM_IS_BROADBAND_BEARER_HSO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_HSO)) +#define MM_IS_BROADBAND_BEARER_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_HSO)) +#define MM_BROADBAND_BEARER_HSO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_HSO, MMBroadbandBearerHsoClass)) + +typedef struct _MMBroadbandBearerHso MMBroadbandBearerHso; +typedef struct _MMBroadbandBearerHsoClass MMBroadbandBearerHsoClass; +typedef struct _MMBroadbandBearerHsoPrivate MMBroadbandBearerHsoPrivate; + +struct _MMBroadbandBearerHso { + MMBroadbandBearer parent; + MMBroadbandBearerHsoPrivate *priv; +}; + +struct _MMBroadbandBearerHsoClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_hso_get_type (void); + +/* Default 3GPP bearer creation implementation */ +void mm_broadband_bearer_hso_new (MMBroadbandModemHso *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_hso_new_finish (GAsyncResult *res, + GError **error); + +gint mm_broadband_bearer_hso_get_connecting_profile_id (MMBroadbandBearerHso *self); + +#endif /* MM_BROADBAND_BEARER_HSO_H */ diff --git a/src/plugins/option/mm-broadband-modem-hso.c b/src/plugins/option/mm-broadband-modem-hso.c new file mode 100644 index 00000000..a2cc1770 --- /dev/null +++ b/src/plugins/option/mm-broadband-modem-hso.c @@ -0,0 +1,788 @@ +/* -*- 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 hso) 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-modem-helpers.h" +#include "mm-log-object.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-location.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-hso.h" +#include "mm-broadband-bearer-hso.h" +#include "mm-bearer-list.h" +#include "mm-shared-option.h" + +static void shared_option_init (MMSharedOption *iface); +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); + +static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemHso, mm_broadband_modem_hso, MM_TYPE_BROADBAND_MODEM_OPTION, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_OPTION, shared_option_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)); + +struct _MMBroadbandModemHsoPrivate { + /* Regex for connected notifications */ + GRegex *_owancall_regex; + + MMModemLocationSource enabled_sources; +}; + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + + g_object_unref (task); +} + +static void +broadband_bearer_hso_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_hso_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + + g_object_unref (task); +} + +static void +modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + if (mm_bearer_properties_get_ip_type (properties) & + (MM_BEARER_IP_FAMILY_IPV6 | MM_BEARER_IP_FAMILY_IPV4V6)) { + mm_obj_dbg (self, "creating generic bearer (IPv6 requested)..."); + mm_broadband_bearer_new (MM_BROADBAND_MODEM (self), + properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_new_ready, + task); + return; + } + + mm_obj_dbg (self, "creating HSO bearer..."); + mm_broadband_bearer_hso_new (MM_BROADBAND_MODEM_HSO (self), + properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_hso_new_ready, + task); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_unlock_retries_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + int pin1, puk1; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + response = mm_strip_tag (response, "_OERCN:"); + if (sscanf (response, " %d, %d", &pin1, &puk1) == 2) { + MMUnlockRetries *retries; + retries = mm_unlock_retries_new (); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1); + g_task_return_pointer (task, retries, g_object_unref); + } else { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid unlock retries response: '%s'", + response); + } + g_object_unref (task); +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "_OERCN?", + 3, + FALSE, + (GAsyncReadyCallback)load_unlock_retries_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +typedef struct { + guint cid; + MMBearerConnectionStatus status; +} BearerListReportStatusForeachContext; + +static void +bearer_list_report_status_foreach (MMBaseBearer *bearer, + BearerListReportStatusForeachContext *ctx) +{ + gint profile_id; + gint connecting_profile_id; + + if (!MM_IS_BROADBAND_BEARER_HSO (bearer)) + return; + + /* The profile ID in the base bearer is set only once the modem is connected */ + profile_id = mm_base_bearer_get_profile_id (bearer); + + /* The profile ID in the hso bearer is available during the connecting phase */ + connecting_profile_id = mm_broadband_bearer_hso_get_connecting_profile_id (MM_BROADBAND_BEARER_HSO (bearer)); + + if ((profile_id != (gint)ctx->cid) && (connecting_profile_id != (gint)ctx->cid)) + return; + + mm_base_bearer_report_connection_status (MM_BASE_BEARER (bearer), ctx->status); +} + +static void +hso_connection_status_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHso *self) +{ + g_autoptr(MMBearerList) list = NULL; + BearerListReportStatusForeachContext ctx; + guint cid; + guint status; + + /* Ensure we got proper parsed values */ + if (!mm_get_uint_from_match_info (match_info, 1, &cid) || + !mm_get_uint_from_match_info (match_info, 2, &status)) + return; + + /* Setup context */ + ctx.cid = cid; + ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN; + + switch (status) { + case 1: + ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED; + break; + case 3: + ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED; + break; + case 0: + ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED; + break; + default: + break; + } + + /* If unknown status, don't try to report anything */ + if (ctx.status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) + return; + + /* If empty bearer list, nothing else to do */ + g_object_get (self, + MM_IFACE_MODEM_BEARER_LIST, &list, + NULL); + + /* Will report status only in the bearer with the specific CID */ + if (list) + mm_bearer_list_foreach (list, (MMBearerListForeachFunc)bearer_list_report_status_foreach, &ctx); +} + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + mm_port_serial_at_add_unsolicited_msg_handler ( + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex, + (MMPortSerialAtUnsolicitedMsgFn)hso_connection_status_changed, + self, + NULL); + + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static void +parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own cleanup first */ + mm_port_serial_at_add_unsolicited_msg_handler ( + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex, + NULL, NULL, NULL); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +static MMModemLocationSource +location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + + sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Now our own check. + * + * We could issue AT_OIFACE? to list the interfaces currently enabled in the + * module, to see if there is a 'GPS' interface enabled. But we'll just go + * and see if there is already a 'GPS control' AT port and a raw serial 'GPS' + * port grabbed. + * + * NOTE: A deeper implementation could handle the situation where the GPS + * interface is found disabled in AT_OIFACE?. In this case, we could issue + * AT_OIFACE="GPS",1 to enable it (and AT_OIFACE="GPS",0 to disable it), but + * enabling/disabling GPS involves a complete reboot of the modem, which is + * probably not the desired thing here. + */ + if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)) && + mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self))) + sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED); + + /* So we're done, complete */ + g_task_return_int (task, sources); + g_object_unref (task); +} + +static void +location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_location_parent->load_capabilities ( + self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Enable/Disable location gathering (Location interface) */ + +typedef struct { + MMModemLocationSource source; +} LocationGatheringContext; + +/******************************/ +/* Disable location gathering */ + +static gboolean +disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LocationGatheringContext *ctx; + MMPortSerialGps *gps_port; + GError *error = NULL; + + mm_base_modem_at_command_full_finish (self, res, &error); + + ctx = g_task_get_task_data (task); + + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + /* Even if we get an error here, we try to close the GPS port */ + gps_port = mm_base_modem_peek_port_gps (self); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemHso *hso = MM_BROADBAND_MODEM_HSO (self); + gboolean stop_gps = FALSE; + LocationGatheringContext *ctx; + GTask *task; + + ctx = g_new (LocationGatheringContext, 1); + ctx->source = source; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + /* Only stop GPS engine if no GPS-related sources enabled */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + hso->priv->enabled_sources &= ~source; + + if (!(hso->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + stop_gps = TRUE; + } + + if (stop_gps) { + /* We enable continuous GPS fixes with AT_OGPS=0 */ + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)), + "_OGPS=0", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_disabled_ready, + task); + return; + } + + /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************/ +/* Enable location gathering */ + +static gboolean +enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_enabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LocationGatheringContext *ctx; + GError *error = NULL; + + if (!mm_base_modem_at_command_full_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMPortSerialGps *gps_port; + + gps_port = mm_base_modem_peek_port_gps (self); + if (!gps_port || + !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't open raw GPS serial port"); + } else + g_task_return_boolean (task, TRUE); + } + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemHso *self = MM_BROADBAND_MODEM_HSO (_self); + LocationGatheringContext *ctx; + gboolean start_gps = FALSE; + GError *error = NULL; + + if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Now our own enabling */ + + ctx = g_task_get_task_data (task); + + /* NMEA, RAW and UNMANAGED are all enabled in the same way */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + /* Only start GPS engine if not done already. + * NOTE: interface already takes care of making sure that raw/nmea and + * unmanaged are not enabled at the same time */ + if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + start_gps = TRUE; + self->priv->enabled_sources |= ctx->source; + } + + if (start_gps) { + /* We enable continuous GPS fixes with AT_OGPS=2 */ + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)), + "_OGPS=2", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_enabled_ready, + task); + return; + } + + /* For any other location (e.g. 3GPP), or if GPS already running just return */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LocationGatheringContext *ctx; + GTask *task; + + ctx = g_new (LocationGatheringContext, 1); + ctx->source = source; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + /* Chain up parent's gathering enable */ + iface_modem_location_parent->enable_location_gathering ( + self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +static void +setup_ports (MMBroadbandModem *self) +{ + MMPortSerialAt *gps_control_port; + MMPortSerialGps *gps_data_port; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_hso_parent_class)->setup_ports (self); + + /* _OWANCALL unsolicited messages are only expected in the primary port. */ + mm_port_serial_at_add_unsolicited_msg_handler ( + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex, + NULL, NULL, NULL); + + g_object_set (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + MM_PORT_SERIAL_SEND_DELAY, (guint64) 0, + /* built-in echo removal conflicts with unsolicited _OWANCALL + * messages, which are not <CR><LF> prefixed. */ + MM_PORT_SERIAL_AT_REMOVE_ECHO, FALSE, + NULL); + + gps_control_port = mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)); + gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_control_port && gps_data_port) { + /* It may happen that the modem was started with GPS already enabled, or + * maybe ModemManager got rebooted and it was left enabled before. We'll make + * sure that it is disabled when we initialize the modem */ + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + gps_control_port, + "_OGPS=0", + 3, FALSE, FALSE, NULL, NULL, NULL); + + /* Add handler for the NMEA traces */ + mm_port_serial_gps_add_trace_handler (gps_data_port, + (MMPortSerialGpsTraceFn)trace_received, + self, + NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemHso * +mm_broadband_modem_hso_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_HSO, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer (AT) and HSO bearer (NET) supported */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemHso *self = MM_BROADBAND_MODEM_HSO (object); + + g_regex_unref (self->priv->_owancall_regex); + + G_OBJECT_CLASS (mm_broadband_modem_hso_parent_class)->finalize (object); +} + +static void +mm_broadband_modem_hso_init (MMBroadbandModemHso *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_HSO, + MMBroadbandModemHsoPrivate); + + self->priv->_owancall_regex = g_regex_new ("_OWANCALL: (\\d),\\s*(\\d)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE; +} + +static void +shared_option_init (MMSharedOption *iface) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface->create_sim = mm_shared_option_create_sim; + iface->create_sim_finish = mm_shared_option_create_sim_finish; + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; + + /* HSO modems don't need the extra 10s wait after powering up */ + iface->modem_after_power_up = NULL; + iface->modem_after_power_up_finish = NULL; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = location_load_capabilities; + iface->load_capabilities_finish = location_load_capabilities_finish; + iface->enable_location_gathering = enable_location_gathering; + iface->enable_location_gathering_finish = enable_location_gathering_finish; + iface->disable_location_gathering = disable_location_gathering; + iface->disable_location_gathering_finish = disable_location_gathering_finish; +} + +static void +mm_broadband_modem_hso_class_init (MMBroadbandModemHsoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemHsoPrivate)); + + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/option/mm-broadband-modem-hso.h b/src/plugins/option/mm-broadband-modem-hso.h new file mode 100644 index 00000000..b918719c --- /dev/null +++ b/src/plugins/option/mm-broadband-modem-hso.h @@ -0,0 +1,51 @@ +/* -*- 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 hso) 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_HSO_H +#define MM_BROADBAND_MODEM_HSO_H + +#include "mm-broadband-modem-option.h" + +#define MM_TYPE_BROADBAND_MODEM_HSO (mm_broadband_modem_hso_get_type ()) +#define MM_BROADBAND_MODEM_HSO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_HSO, MMBroadbandModemHso)) +#define MM_BROADBAND_MODEM_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_HSO, MMBroadbandModemHsoClass)) +#define MM_IS_BROADBAND_MODEM_HSO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_HSO)) +#define MM_IS_BROADBAND_MODEM_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_HSO)) +#define MM_BROADBAND_MODEM_HSO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_HSO, MMBroadbandModemHsoClass)) + +typedef struct _MMBroadbandModemHso MMBroadbandModemHso; +typedef struct _MMBroadbandModemHsoClass MMBroadbandModemHsoClass; +typedef struct _MMBroadbandModemHsoPrivate MMBroadbandModemHsoPrivate; + +struct _MMBroadbandModemHso { + MMBroadbandModemOption parent; + MMBroadbandModemHsoPrivate *priv; +}; + +struct _MMBroadbandModemHsoClass{ + MMBroadbandModemOptionClass parent; +}; + +GType mm_broadband_modem_hso_get_type (void); + +MMBroadbandModemHso *mm_broadband_modem_hso_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_HSO_H */ diff --git a/src/plugins/option/mm-broadband-modem-option.c b/src/plugins/option/mm-broadband-modem-option.c new file mode 100644 index 00000000..dcecd5b0 --- /dev/null +++ b/src/plugins/option/mm-broadband-modem-option.c @@ -0,0 +1,1228 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-modem-helpers.h" +#include "mm-log-object.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-option.h" +#include "mm-shared-option.h" + +static void shared_option_init (MMSharedOption *iface); +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemOption, mm_broadband_modem_option, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_OPTION, shared_option_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)) + +struct _MMBroadbandModemOptionPrivate { + /* Regex for access-technology related notifications */ + GRegex *_ossysi_regex; + GRegex *_octi_regex; + GRegex *_ouwcti_regex; + + /* Regex for signal quality related notifications */ + GRegex *_osigq_regex; + + /* Regex for other notifications to ignore */ + GRegex *ignore_regex; + + guint after_power_up_wait_id; +}; + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMModemModeCombination mode; + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G, 2G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + /* 2G and 3G, 3G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + const gchar *response; + const gchar *str; + gint a, b; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + str = mm_strip_tag (response, "_OPSYS:"); + + if (!sscanf (str, "%d,%d", &a, &b)) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse OPSYS response: '%s'", + response); + return FALSE; + } + + switch (a) { + case 0: + *allowed = MM_MODEM_MODE_2G; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + case 1: + *allowed = MM_MODEM_MODE_3G; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + case 2: + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_2G; + return TRUE; + case 3: + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_3G; + return TRUE; + case 5: /* any */ + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + default: + break; + } + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse unexpected OPSYS response: '%s'", + response); + return FALSE; +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "_OPSYS?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +allowed_mode_update_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); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *command; + gint option_mode = -1; + + task = g_task_new (self, NULL, callback, user_data); + + if (allowed == MM_MODEM_MODE_2G) + option_mode = 0; + else if (allowed == MM_MODEM_MODE_3G) + option_mode = 1; + else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { + if (preferred == MM_MODEM_MODE_2G) + option_mode = 2; + else if (preferred == MM_MODEM_MODE_3G) + option_mode = 3; + else /* none preferred, so AUTO */ + option_mode = 5; + } else if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE) + option_mode = 5; + + if (option_mode < 0) { + 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_object_unref (task); + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("AT_OPSYS=%d,2", option_mode); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)allowed_mode_update_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +typedef enum { + ACCESS_TECHNOLOGIES_STEP_FIRST, + ACCESS_TECHNOLOGIES_STEP_OSSYS, + ACCESS_TECHNOLOGIES_STEP_OCTI, + ACCESS_TECHNOLOGIES_STEP_OWCTI, + ACCESS_TECHNOLOGIES_STEP_LAST +} AccessTechnologiesStep; + +typedef struct { + MMModemAccessTechnology access_technology; + gboolean check_2g; + gboolean check_3g; + AccessTechnologiesStep step; +} AccessTechnologiesContext; + +static void load_access_technologies_step (GTask *task); + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + /* We are reporting ALL 3GPP access technologies here */ + *access_technologies = (MMModemAccessTechnology) value; + *mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK; + return TRUE; +} + +static gboolean +ossys_to_mm (gchar ossys, + MMModemAccessTechnology *access_technology) +{ + if (ossys == '0') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + return TRUE; + } + + if (ossys == '2') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + return TRUE; + } + + if (ossys == '3') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + return TRUE; + } + + return FALSE; +} + +static gboolean +parse_ossys_response (const gchar *response, + MMModemAccessTechnology *access_technology) +{ + MMModemAccessTechnology current = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + const gchar *p; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + p = mm_strip_tag (response, "_OSSYS:"); + r = g_regex_new ("(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL); + g_assert (r != NULL); + + g_regex_match (r, p, 0, &match_info); + if (g_match_info_matches (match_info)) { + g_autofree gchar *str = NULL; + + str = g_match_info_fetch (match_info, 2); + if (str && ossys_to_mm (str[0], ¤t)) { + *access_technology = current; + return TRUE; + } + } + return FALSE; +} + +static void +ossys_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + AccessTechnologiesContext *ctx; + const gchar *response; + + ctx = g_task_get_task_data (task); + + /* If for some reason the OSSYS request failed, still try to check + * explicit 2G/3G mode with OCTI and OWCTI; maybe we'll get something. + */ + response = mm_base_modem_at_command_finish (self, res, NULL); + /* Response is _OSSYS: <n>,<act> so we must skip the <n> */ + if (response && + parse_ossys_response (response, &ctx->access_technology)) { + /* If the OSSYS response indicated a generic access tech type + * then only check for more specific access tech of that type. + */ + if (ctx->access_technology == MM_MODEM_ACCESS_TECHNOLOGY_GPRS) + ctx->check_3g = FALSE; + else if (ctx->access_technology == MM_MODEM_ACCESS_TECHNOLOGY_UMTS) + ctx->check_2g = FALSE; + } + + /* Go on to next step */ + ctx->step++; + load_access_technologies_step (task); +} + +static gboolean +octi_to_mm (gchar octi, + MMModemAccessTechnology *access_technology) +{ + if (octi == '1') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GSM; + return TRUE; + } + + if (octi == '2') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + return TRUE; + } + + if (octi == '3') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + return TRUE; + } + + return FALSE; +} + +static gboolean +parse_octi_response (const gchar *response, + MMModemAccessTechnology *access_technology) +{ + MMModemAccessTechnology current = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + const gchar *p; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + p = mm_strip_tag (response, "_OCTI:"); + r = g_regex_new ("(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL); + g_assert (r != NULL); + + g_regex_match (r, p, 0, &match_info); + if (g_match_info_matches (match_info)) { + g_autofree gchar *str = NULL; + + str = g_match_info_fetch (match_info, 2); + if (str && octi_to_mm (str[0], ¤t)) { + *access_technology = current; + return TRUE; + } + } + return FALSE; +} + +static void +octi_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + AccessTechnologiesContext *ctx; + MMModemAccessTechnology octi = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + const gchar *response; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (self, res, NULL); + if (response && + parse_octi_response (response, &octi)) { + /* If current tech is 2G or unknown then use the more specific + * OCTI response. + */ + if (ctx->access_technology < MM_MODEM_ACCESS_TECHNOLOGY_UMTS) + ctx->access_technology = octi; + } + + /* Go on to next step */ + ctx->step++; + load_access_technologies_step (task); +} + +static gboolean +owcti_to_mm (gchar owcti, MMModemAccessTechnology *access_technology) +{ + if (owcti == '1') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + return TRUE; + } + + if (owcti == '2') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; + return TRUE; + } + + if (owcti == '3') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSUPA; + return TRUE; + } + + if (owcti == '4') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSPA; + return TRUE; + } + + return FALSE; +} + +static gboolean +parse_owcti_response (const gchar *response, + MMModemAccessTechnology *access_technology) +{ + response = mm_strip_tag (response, "_OWCTI:"); + return owcti_to_mm (*response, access_technology); +} + +static void +owcti_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + AccessTechnologiesContext *ctx; + MMModemAccessTechnology owcti = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + const gchar *response; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (self, res, NULL); + if (response && + parse_owcti_response (response, &owcti)) { + ctx->access_technology = owcti; + } + + /* Go on to next step */ + ctx->step++; + load_access_technologies_step (task); +} + +static void +load_access_technologies_step (GTask *task) +{ + MMBroadbandModemOption *self; + AccessTechnologiesContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case ACCESS_TECHNOLOGIES_STEP_FIRST: + ctx->step++; + /* fall through */ + + case ACCESS_TECHNOLOGIES_STEP_OSSYS: + mm_base_modem_at_command (MM_BASE_MODEM (self), + "_OSSYS?", + 3, + FALSE, + (GAsyncReadyCallback)ossys_query_ready, + task); + break; + + case ACCESS_TECHNOLOGIES_STEP_OCTI: + if (ctx->check_2g) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "_OCTI?", + 3, + FALSE, + (GAsyncReadyCallback)octi_query_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ACCESS_TECHNOLOGIES_STEP_OWCTI: + if (ctx->check_3g) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "_OWCTI?", + 3, + FALSE, + (GAsyncReadyCallback)owcti_query_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ACCESS_TECHNOLOGIES_STEP_LAST: + /* All done, set result and complete */ + g_task_return_int (task, ctx->access_technology); + g_object_unref (task); + break; + + default: + g_assert_not_reached (); + } +} + +static void +run_access_technology_loading_sequence (MMIfaceModem *self, + AccessTechnologiesStep first, + gboolean check_2g, + gboolean check_3g, + GAsyncReadyCallback callback, + gpointer user_data) +{ + AccessTechnologiesContext *ctx; + GTask *task; + + ctx = g_new (AccessTechnologiesContext, 1); + ctx->step = first; + ctx->check_2g = check_2g; + ctx->check_3g = check_3g; + ctx->access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + load_access_technologies_step (task); +} + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + run_access_technology_loading_sequence (self, + ACCESS_TECHNOLOGIES_STEP_FIRST, + TRUE, /* check 2g */ + TRUE, /* check 3g */ + callback, + user_data); +} + +/*****************************************************************************/ +/* After power up (Modem interface) */ + +static gboolean +modem_after_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +after_power_up_wait_cb (GTask *task) +{ + MMBroadbandModemOption *self; + + self = g_task_get_source_object (task); + self->priv->after_power_up_wait_id = 0; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + + return G_SOURCE_REMOVE; +} + +static void +modem_after_power_up (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemOption *self = MM_BROADBAND_MODEM_OPTION (_self); + + /* Some Option devices return OK on +CFUN=1 right away but need some time + * to finish initialization. + */ + g_warn_if_fail (self->priv->after_power_up_wait_id == 0); + self->priv->after_power_up_wait_id = + g_timeout_add_seconds (10, + (GSourceFunc)after_power_up_wait_cb, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* IMSI loading (3GPP interface) */ + +static gchar * +modem_3gpp_load_imei_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + gchar *imei; + gchar *comma; + + imei = g_strdup (mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error)); + if (!imei) + return NULL; + + /* IMEI reported by Option modems contain the IMEI plus something else: + * + * (ttyHS4): --> 'AT+CGSN<CR>' + * (ttyHS4): <-- '<CR><LF>357516032005989,TR19A8P11R<CR><LF><CR><LF>OK<CR><LF>' + */ + comma = strchr (imei, ','); + if (comma) + *comma = '\0'; + + return imei; +} + +static void +modem_3gpp_load_imei (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CGSN", + 3, + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static void +option_ossys_tech_changed (MMPortSerialAt *port, + GMatchInfo *info, + MMBroadbandModemOption *self) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + gchar *str; + + str = g_match_info_fetch (info, 1); + if (str) { + ossys_to_mm (str[0], &act); + g_free (str); + } + + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + act, + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); + + /* _OSSYSI only indicates general 2G/3G mode, so queue up some explicit + * access technology requests. + */ + if (act == MM_MODEM_ACCESS_TECHNOLOGY_GPRS) + run_access_technology_loading_sequence (MM_IFACE_MODEM (self), + ACCESS_TECHNOLOGIES_STEP_OCTI, + TRUE, /* check 2g */ + FALSE, /* check 3g */ + NULL, + NULL); + else if (act == MM_MODEM_ACCESS_TECHNOLOGY_UMTS) + run_access_technology_loading_sequence (MM_IFACE_MODEM (self), + ACCESS_TECHNOLOGIES_STEP_OWCTI, + FALSE, /* check 2g */ + TRUE, /* check 3g */ + NULL, + NULL); +} + +static void +option_2g_tech_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemOption *self) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + gchar *str; + + str = g_match_info_fetch (match_info, 1); + if (str && octi_to_mm (str[0], &act)) + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + act, + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); + g_free (str); +} + +static void +option_3g_tech_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemOption *self) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + gchar *str; + + str = g_match_info_fetch (match_info, 1); + if (str && owcti_to_mm (str[0], &act)) + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + act, + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); + g_free (str); +} + +static void +option_signal_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemOption *self) +{ + gchar *str; + guint quality = 0; + + str = g_match_info_fetch (match_info, 1); + if (str) { + quality = atoi (str); + g_free (str); + } + + if (quality == 99) { + /* 99 means unknown */ + quality = 0; + } else { + /* Normalize the quality */ + quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31; + } + + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); +} + +static void +set_unsolicited_events_handlers (MMBroadbandModemOption *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* Access technology related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->_ossysi_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)option_ossys_tech_changed : NULL, + enable ? self : NULL, + NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->_octi_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)option_2g_tech_changed : NULL, + enable ? self : NULL, + NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->_ouwcti_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)option_3g_tech_changed : NULL, + enable ? self : NULL, + NULL); + + /* Signal quality related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->_osigq_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)option_signal_changed : NULL, + enable ? self : NULL, + NULL); + + /* Other unsolicited events to always ignore */ + if (!enable) + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->ignore_regex, + NULL, NULL, NULL); + } +} + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_OPTION (self), TRUE); + g_task_return_boolean (task, TRUE); + } + + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static void +parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own cleanup first */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_OPTION (self), FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Enabling unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +own_enable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static const MMBaseModemAtCommand unsolicited_enable_sequence[] = { + { "_OSSYS=1", 3, FALSE, NULL }, + { "_OCTI=1", 3, FALSE, NULL }, + { "_OUWCTI=1", 3, FALSE, NULL }, + { "_OSQI=1", 3, FALSE, NULL }, + { NULL } +}; + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + } + + /* Our own enable now */ + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_enable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_enable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's enable */ + iface_modem_3gpp_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Disabling unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static const MMBaseModemAtCommand unsolicited_disable_sequence[] = { + { "_OSSYS=0", 3, FALSE, NULL }, + { "_OCTI=0", 3, FALSE, NULL }, + { "_OUWCTI=0", 3, FALSE, NULL }, + { "_OSQI=0", 3, FALSE, NULL }, + { NULL } +}; + +static void +parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +own_disable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Next, chain up parent's disable */ + iface_modem_3gpp_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own disable first */ + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_disable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_disable_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *self) +{ + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_option_parent_class)->setup_ports (self); + + /* Now reset the unsolicited messages we'll handle when enabled */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_OPTION (self), FALSE); +} + +/*****************************************************************************/ + +static gboolean +is_nozomi (const gchar **drivers) +{ + if (drivers) { + guint i; + + for (i = 0; drivers[i]; i++) { + if (g_str_equal (drivers[i], "nozomi")) + return TRUE; + } + } + + return FALSE; +} + +MMBroadbandModemOption * +mm_broadband_modem_option_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + MMModem3gppFacility ignored; + + /* Ignore PH-SIM facility in 'nozomi' managed modems */ + ignored = is_nozomi (drivers) ? MM_MODEM_3GPP_FACILITY_PH_SIM : MM_MODEM_3GPP_FACILITY_NONE; + + return g_object_new (MM_TYPE_BROADBAND_MODEM_OPTION, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + MM_IFACE_MODEM_3GPP_IGNORED_FACILITY_LOCKS, ignored, + NULL); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemOption *self = MM_BROADBAND_MODEM_OPTION (object); + + g_regex_unref (self->priv->_ossysi_regex); + g_regex_unref (self->priv->_octi_regex); + g_regex_unref (self->priv->_ouwcti_regex); + g_regex_unref (self->priv->_osigq_regex); + g_regex_unref (self->priv->ignore_regex); + + G_OBJECT_CLASS (mm_broadband_modem_option_parent_class)->finalize (object); +} + +static void +mm_broadband_modem_option_init (MMBroadbandModemOption *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_OPTION, + MMBroadbandModemOptionPrivate); + self->priv->after_power_up_wait_id = 0; + + /* Prepare regular expressions to setup */ + self->priv->_ossysi_regex = g_regex_new ("\\r\\n_OSSYSI:\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->_octi_regex = g_regex_new ("\\r\\n_OCTI:\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->_ouwcti_regex = g_regex_new ("\\r\\n_OUWCTI:\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->_osigq_regex = g_regex_new ("\\r\\n_OSIGQ:\\s*(\\d+),(\\d)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ignore_regex = g_regex_new ("\\r\\n\\+PACSP0\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +static void +shared_option_init (MMSharedOption *iface) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->create_sim = mm_shared_option_create_sim; + iface->create_sim_finish = mm_shared_option_create_sim_finish; + iface->modem_after_power_up = modem_after_power_up; + iface->modem_after_power_up_finish = modem_after_power_up_finish; + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->load_imei = modem_3gpp_load_imei; + iface->load_imei_finish = modem_3gpp_load_imei_finish; + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish; +} + +static void +mm_broadband_modem_option_class_init (MMBroadbandModemOptionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemOptionPrivate)); + + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/option/mm-broadband-modem-option.h b/src/plugins/option/mm-broadband-modem-option.h new file mode 100644 index 00000000..faf0595e --- /dev/null +++ b/src/plugins/option/mm-broadband-modem-option.h @@ -0,0 +1,51 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_OPTION_H +#define MM_BROADBAND_MODEM_OPTION_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_OPTION (mm_broadband_modem_option_get_type ()) +#define MM_BROADBAND_MODEM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_OPTION, MMBroadbandModemOption)) +#define MM_BROADBAND_MODEM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_OPTION, MMBroadbandModemOptionClass)) +#define MM_IS_BROADBAND_MODEM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_OPTION)) +#define MM_IS_BROADBAND_MODEM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_OPTION)) +#define MM_BROADBAND_MODEM_OPTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_OPTION, MMBroadbandModemOptionClass)) + +typedef struct _MMBroadbandModemOption MMBroadbandModemOption; +typedef struct _MMBroadbandModemOptionClass MMBroadbandModemOptionClass; +typedef struct _MMBroadbandModemOptionPrivate MMBroadbandModemOptionPrivate; + +struct _MMBroadbandModemOption { + MMBroadbandModem parent; + MMBroadbandModemOptionPrivate *priv; +}; + +struct _MMBroadbandModemOptionClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_option_get_type (void); + +MMBroadbandModemOption *mm_broadband_modem_option_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_OPTION_H */ diff --git a/src/plugins/option/mm-plugin-hso.c b/src/plugins/option/mm-plugin-hso.c new file mode 100644 index 00000000..9a28ca64 --- /dev/null +++ b/src/plugins/option/mm-plugin-hso.c @@ -0,0 +1,202 @@ +/* -*- 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 hso) 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-private-boxed-types.h" +#include "mm-plugin-hso.h" +#include "mm-broadband-modem-hso.h" +#include "mm-log-object.h" + +G_DEFINE_TYPE (MMPluginHso, mm_plugin_hso, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ +/* Custom init */ + +#define TAG_HSO_AT_CONTROL "hso-at-control" +#define TAG_HSO_AT_APP "hso-at-app" +#define TAG_HSO_AT_MODEM "hso-at-modem" +#define TAG_HSO_AT_GPS_CONTROL "hso-at-gps-control" +#define TAG_HSO_GPS "hso-gps" +#define TAG_HSO_DIAG "hso-diag" + +static gboolean +hso_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +hso_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMKernelDevice *kernel_port; + GTask *task; + const gchar *subsys, *sysfs_path; + + subsys = mm_port_probe_get_port_subsys (probe); + kernel_port = mm_port_probe_peek_port (probe); + sysfs_path = mm_kernel_device_get_sysfs_path (kernel_port); + + if (g_str_equal (subsys, "tty")) { + gchar *hsotype_path; + gchar *contents = NULL; + + hsotype_path = g_build_filename (sysfs_path, "hsotype", NULL); + if (g_file_get_contents (hsotype_path, &contents, NULL, NULL)) { + mm_obj_dbg (probe, "HSO port type %s: %s", hsotype_path, contents); + if (g_str_has_prefix (contents, "Control")) { + g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_CONTROL, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, TRUE); + } else if (g_str_has_prefix (contents, "Application")) { + g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_APP, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, TRUE); + } else if (g_str_has_prefix (contents, "Modem")) { + g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_MODEM, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, TRUE); + } else if (g_str_has_prefix (contents, "GPS Control")) { + g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_GPS_CONTROL, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, TRUE); + } else if (g_str_has_prefix (contents, "GPS")) { + /* Not an AT port, but the port to grab GPS traces */ + g_object_set_data (G_OBJECT (probe), TAG_HSO_GPS, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, FALSE); + mm_port_probe_set_result_qcdm (probe, FALSE); + } else if (g_str_has_prefix (contents, "Diag")) { + g_object_set_data (G_OBJECT (probe), TAG_HSO_DIAG, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, FALSE); + + /* Don't automatically tag as QCDM, as the 'hso' driver reports + * a DIAG port for some Icera-based modems, which don't have + * QCDM ports since they aren't made by Qualcomm. + */ + } + g_free (contents); + } + g_free (hsotype_path); + } + + task = g_task_new (probe, NULL, callback, user_data); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_hso_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +static gboolean +grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + const gchar *subsys; + MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE; + MMPortType port_type; + + subsys = mm_port_probe_get_port_subsys (probe); + port_type = mm_port_probe_get_port_type (probe); + + /* Detect AT port types */ + if (g_str_equal (subsys, "tty")) { + if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_CONTROL)) + pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY; + else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_APP)) + pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY; + else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_GPS_CONTROL)) + pflags = MM_PORT_SERIAL_AT_FLAG_GPS_CONTROL; + else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_MODEM)) + pflags = MM_PORT_SERIAL_AT_FLAG_PPP; + else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_GPS)) { + /* Not an AT port, but the port to grab GPS traces */ + g_assert (port_type == MM_PORT_TYPE_UNKNOWN); + port_type = MM_PORT_TYPE_GPS; + } + } + + return mm_base_modem_grab_port (modem, + mm_port_probe_peek_port (probe), + port_type, + pflags, + error); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", NULL }; + static const gchar *drivers[] = { "hso", NULL }; + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (hso_custom_init), + .finish = G_CALLBACK (hso_custom_init_finish), + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_HSO, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_DRIVERS, drivers, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QCDM, TRUE, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + MM_PLUGIN_SEND_DELAY, (guint64) 0, + NULL)); +} + +static void +mm_plugin_hso_init (MMPluginHso *self) +{ +} + +static void +mm_plugin_hso_class_init (MMPluginHsoClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = grab_port; +} diff --git a/src/plugins/option/mm-plugin-hso.h b/src/plugins/option/mm-plugin-hso.h new file mode 100644 index 00000000..5ef13439 --- /dev/null +++ b/src/plugins/option/mm-plugin-hso.h @@ -0,0 +1,42 @@ +/* -*- 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 hso) 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_HSO_H +#define MM_PLUGIN_HSO_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_HSO (mm_plugin_hso_get_type ()) +#define MM_PLUGIN_HSO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_HSO, MMPluginHso)) +#define MM_PLUGIN_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_HSO, MMPluginHsoClass)) +#define MM_IS_PLUGIN_HSO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_HSO)) +#define MM_IS_PLUGIN_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_HSO)) +#define MM_PLUGIN_HSO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_HSO, MMPluginHsoClass)) + +typedef struct { + MMPlugin parent; +} MMPluginHso; + +typedef struct { + MMPluginClass parent; +} MMPluginHsoClass; + +GType mm_plugin_hso_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_HSO_H */ diff --git a/src/plugins/option/mm-plugin-option.c b/src/plugins/option/mm-plugin-option.c new file mode 100644 index 00000000..4dcb55a1 --- /dev/null +++ b/src/plugins/option/mm-plugin-option.c @@ -0,0 +1,121 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-private-boxed-types.h" +#include "mm-plugin-option.h" +#include "mm-broadband-modem-option.h" + +G_DEFINE_TYPE (MMPluginOption, mm_plugin_option, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_option_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +static gboolean +grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE; + MMKernelDevice *port; + gint usbif; + + /* The Option plugin cannot do anything with non-AT ports */ + if (!mm_port_probe_is_at (probe)) { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Ignoring non-AT port"); + return FALSE; + } + + port = mm_port_probe_peek_port (probe); + + /* Genuine Option NV devices are always supposed to use USB interface 0 as + * the modem/data port, per mail with Option engineers. Only this port + * will emit responses to dialing commands. + */ + usbif = mm_kernel_device_get_interface_number (port); + if (usbif == 0) + pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY | MM_PORT_SERIAL_AT_FLAG_PPP; + + return mm_base_modem_grab_port (modem, + port, + MM_PORT_TYPE_AT, /* we only allow AT ports here */ + pflags, + error); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + static const guint16 vendor_ids[] = { 0x0af0, /* Option USB devices */ + 0x1931, /* Nozomi CardBus devices */ + 0 }; + static const gchar *drivers[] = { "option1", "option", "nozomi", NULL }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_OPTION, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_DRIVERS, drivers, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + NULL)); +} + +static void +mm_plugin_option_init (MMPluginOption *self) +{ +} + +static void +mm_plugin_option_class_init (MMPluginOptionClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = grab_port; +} diff --git a/src/plugins/option/mm-plugin-option.h b/src/plugins/option/mm-plugin-option.h new file mode 100644 index 00000000..275fc403 --- /dev/null +++ b/src/plugins/option/mm-plugin-option.h @@ -0,0 +1,42 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_OPTION_H +#define MM_PLUGIN_OPTION_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_OPTION (mm_plugin_option_get_type ()) +#define MM_PLUGIN_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_OPTION, MMPluginOption)) +#define MM_PLUGIN_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_OPTION, MMPluginOptionClass)) +#define MM_IS_PLUGIN_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_OPTION)) +#define MM_IS_PLUGIN_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_OPTION)) +#define MM_PLUGIN_OPTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_OPTION, MMPluginOptionClass)) + +typedef struct { + MMPlugin parent; +} MMPluginOption; + +typedef struct { + MMPluginClass parent; +} MMPluginOptionClass; + +GType mm_plugin_option_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_OPTION_H */ diff --git a/src/plugins/option/mm-shared-option.c b/src/plugins/option/mm-shared-option.c new file mode 100644 index 00000000..a06888a1 --- /dev/null +++ b/src/plugins/option/mm-shared-option.c @@ -0,0 +1,77 @@ +/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdio.h> + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-iface-modem.h" +#include "mm-sim-option.h" +#include "mm-shared-option.h" + +/*****************************************************************************/ +/* Create SIM (Modem inteface) */ + +MMBaseSim * +mm_shared_option_create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_option_new_finish (res, error); +} + +void +mm_shared_option_create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_sim_option_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + +/*****************************************************************************/ + +static void +shared_option_init (gpointer g_iface) +{ +} + +GType +mm_shared_option_get_type (void) +{ + static GType shared_option_type = 0; + + if (!G_UNLIKELY (shared_option_type)) { + static const GTypeInfo info = { + sizeof (MMSharedOption), /* class_size */ + shared_option_init, /* base_init */ + NULL, /* base_finalize */ + }; + + shared_option_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedOption", &info, 0); + g_type_interface_add_prerequisite (shared_option_type, MM_TYPE_IFACE_MODEM); + } + + return shared_option_type; +} diff --git a/src/plugins/option/mm-shared-option.h b/src/plugins/option/mm-shared-option.h new file mode 100644 index 00000000..0d4baf60 --- /dev/null +++ b/src/plugins/option/mm-shared-option.h @@ -0,0 +1,49 @@ +/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_SHARED_OPTION_H +#define MM_SHARED_OPTION_H + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" + +#define MM_TYPE_SHARED_OPTION (mm_shared_option_get_type ()) +#define MM_SHARED_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_OPTION, MMSharedOption)) +#define MM_IS_SHARED_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_OPTION)) +#define MM_SHARED_OPTION_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_OPTION, MMSharedOption)) + +typedef struct _MMSharedOption MMSharedOption; + +struct _MMSharedOption { + GTypeInterface g_iface; +}; + +GType mm_shared_option_get_type (void); + +void mm_shared_option_create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_shared_option_create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +#endif /* MM_SHARED_OPTION_H */ diff --git a/src/plugins/option/mm-shared.c b/src/plugins/option/mm-shared.c new file mode 100644 index 00000000..3f89d86a --- /dev/null +++ b/src/plugins/option/mm-shared.c @@ -0,0 +1,20 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-shared.h" + +MM_SHARED_DEFINE_MAJOR_VERSION +MM_SHARED_DEFINE_MINOR_VERSION +MM_SHARED_DEFINE_NAME(Option) diff --git a/src/plugins/option/mm-sim-option.c b/src/plugins/option/mm-sim-option.c new file mode 100644 index 00000000..0871c4f2 --- /dev/null +++ b/src/plugins/option/mm-sim-option.c @@ -0,0 +1,84 @@ +/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-sim-option.h" + +G_DEFINE_TYPE (MMSimOption, mm_sim_option, MM_TYPE_BASE_SIM) + +/*****************************************************************************/ + +MMBaseSim * +mm_sim_option_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *sim; + + source = g_async_result_get_source_object (res); + sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!sim) + return NULL; + + /* Only export valid SIMs */ + mm_base_sim_export (MM_BASE_SIM (sim)); + + return MM_BASE_SIM (sim); +} + +void +mm_sim_option_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (MM_TYPE_SIM_OPTION, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_SIM_MODEM, modem, + "active", TRUE, /* by default always active */ + NULL); +} + +static void +mm_sim_option_init (MMSimOption *self) +{ +} + +static void +mm_sim_option_class_init (MMSimOptionClass *klass) +{ + MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass); + + /* Skip managing preferred networks, not supported by Option modems */ + base_sim_class->load_preferred_networks = NULL; + base_sim_class->load_preferred_networks_finish = NULL; + base_sim_class->set_preferred_networks = NULL; + base_sim_class->set_preferred_networks_finish = NULL; +} diff --git a/src/plugins/option/mm-sim-option.h b/src/plugins/option/mm-sim-option.h new file mode 100644 index 00000000..c502a397 --- /dev/null +++ b/src/plugins/option/mm-sim-option.h @@ -0,0 +1,51 @@ +/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_SIM_OPTION_H +#define MM_SIM_OPTION_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-sim.h" + +#define MM_TYPE_SIM_OPTION (mm_sim_option_get_type ()) +#define MM_SIM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_OPTION, MMSimOption)) +#define MM_SIM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_OPTION, MMSimOptionClass)) +#define MM_IS_SIM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_OPTION)) +#define MM_IS_SIM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_OPTION)) +#define MM_SIM_OPTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_OPTION, MMSimOptionClass)) + +typedef struct _MMSimOption MMSimOption; +typedef struct _MMSimOptionClass MMSimOptionClass; + +struct _MMSimOption { + MMBaseSim parent; +}; + +struct _MMSimOptionClass { + MMBaseSimClass parent; +}; + +GType mm_sim_option_get_type (void); + +void mm_sim_option_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_sim_option_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_SIM_OPTION_H */ diff --git a/src/plugins/pantech/mm-broadband-modem-pantech.c b/src/plugins/pantech/mm-broadband-modem-pantech.c new file mode 100644 index 00000000..4e8b58e0 --- /dev/null +++ b/src/plugins/pantech/mm-broadband-modem-pantech.c @@ -0,0 +1,187 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-messaging.h" +#include "mm-errors-types.h" +#include "mm-broadband-modem-pantech.h" +#include "mm-sim-pantech.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_messaging_init (MMIfaceModemMessaging *iface); + +static MMIfaceModemMessaging *iface_modem_messaging_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemPantech, mm_broadband_modem_pantech, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)) + +/*****************************************************************************/ +/* Load supported SMS storages (Messaging interface) */ + +static void +skip_sm_sr_storage (GArray *mem) +{ + guint i = mem->len; + + if (!mem) + return; + + /* Remove SM and SR from the list of supported storages */ + while (i-- > 0) { + if (g_array_index (mem, MMSmsStorage, i) == MM_SMS_STORAGE_SR || + g_array_index (mem, MMSmsStorage, i) == MM_SMS_STORAGE_SM) + g_array_remove_index (mem, i); + } +} + +static gboolean +load_supported_storages_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GArray **mem1, + GArray **mem2, + GArray **mem3, + GError **error) +{ + if (!iface_modem_messaging_parent->load_supported_storages_finish (self, res, mem1, mem2, mem3, error)) + return FALSE; + + skip_sm_sr_storage (*mem1); + skip_sm_sr_storage (*mem2); + skip_sm_sr_storage (*mem3); + return TRUE; +} + +static void +load_supported_storages (MMIfaceModemMessaging *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's loading */ + iface_modem_messaging_parent->load_supported_storages (self, callback, user_data); +} + +/*****************************************************************************/ +/* Create SIM (Modem interface) */ + +static MMBaseSim * +create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_pantech_new_finish (res, error); +} + +static void +create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* New Pantech SIM */ + mm_sim_pantech_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + +/*****************************************************************************/ +/* After SIM unlock (Modem interface) */ + +static gboolean +modem_after_sim_unlock_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +after_sim_unlock_wait_cb (GTask *task) +{ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +modem_after_sim_unlock (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* wait so sim pin is done */ + g_timeout_add_seconds (5, + (GSourceFunc)after_sim_unlock_wait_cb, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ + +MMBroadbandModemPantech * +mm_broadband_modem_pantech_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_PANTECH, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_pantech_init (MMBroadbandModemPantech *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + /* Create Pantech-specific SIM */ + iface->create_sim = create_sim; + iface->create_sim_finish = create_sim_finish; + + iface->modem_after_sim_unlock = modem_after_sim_unlock; + iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish; +} + +static void +iface_modem_messaging_init (MMIfaceModemMessaging *iface) +{ + iface_modem_messaging_parent = g_type_interface_peek_parent (iface); + + iface->load_supported_storages = load_supported_storages; + iface->load_supported_storages_finish = load_supported_storages_finish; +} + +static void +mm_broadband_modem_pantech_class_init (MMBroadbandModemPantechClass *klass) +{ +} diff --git a/src/plugins/pantech/mm-broadband-modem-pantech.h b/src/plugins/pantech/mm-broadband-modem-pantech.h new file mode 100644 index 00000000..4a0a3a27 --- /dev/null +++ b/src/plugins/pantech/mm-broadband-modem-pantech.h @@ -0,0 +1,47 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_PANTECH_H +#define MM_BROADBAND_MODEM_PANTECH_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_PANTECH (mm_broadband_modem_pantech_get_type ()) +#define MM_BROADBAND_MODEM_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_PANTECH, MMBroadbandModemPantech)) +#define MM_BROADBAND_MODEM_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_PANTECH, MMBroadbandModemPantechClass)) +#define MM_IS_BROADBAND_MODEM_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_PANTECH)) +#define MM_IS_BROADBAND_MODEM_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_PANTECH)) +#define MM_BROADBAND_MODEM_PANTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_PANTECH, MMBroadbandModemPantechClass)) + +typedef struct _MMBroadbandModemPantech MMBroadbandModemPantech; +typedef struct _MMBroadbandModemPantechClass MMBroadbandModemPantechClass; + +struct _MMBroadbandModemPantech { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemPantechClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_pantech_get_type (void); + +MMBroadbandModemPantech *mm_broadband_modem_pantech_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_PANTECH_H */ diff --git a/src/plugins/pantech/mm-plugin-pantech.c b/src/plugins/pantech/mm-plugin-pantech.c new file mode 100644 index 00000000..4af1955b --- /dev/null +++ b/src/plugins/pantech/mm-plugin-pantech.c @@ -0,0 +1,161 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-pantech.h" +#include "mm-broadband-modem-pantech.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi.h" +#endif + +G_DEFINE_TYPE (MMPluginPantech, mm_plugin_pantech, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ +/* Custom commands for AT probing + * There's currently no WMC probing plugged in the logic, so We need to detect + * WMC ports ourselves somehow. Just assume that the WMC port will reply "ERROR" + * to the "ATE0" command. + */ +static gboolean +port_probe_response_processor_is_pantech_at (const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + if (error) { + /* Timeout errors are the only ones not fatal; + * they will just go on to the next command. */ + if (g_error_matches (error, + MM_SERIAL_ERROR, + MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { + return FALSE; + } + + /* All other errors indicate NOT an AT port */ + *result = g_variant_new_boolean (FALSE); + return TRUE; + } + + /* No error reported, valid AT port! */ + *result = g_variant_new_boolean (TRUE); + return TRUE; +} + +static const MMPortProbeAtCommand custom_at_probe[] = { + { "ATE0", 3, port_probe_response_processor_is_pantech_at }, + { "ATE0", 3, port_probe_response_processor_is_pantech_at }, + { "ATE0", 3, port_probe_response_processor_is_pantech_at }, + { NULL } +}; + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Pantech modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_pantech_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +static gboolean +grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + MMPortType ptype; + MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE; + + ptype = mm_port_probe_get_port_type (probe); + + /* Always prefer the ttyACM port as PRIMARY AT port */ + if (ptype == MM_PORT_TYPE_AT && + g_str_has_prefix (mm_port_probe_get_port_name (probe), "ttyACM")) { + pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY; + } + + return mm_base_modem_grab_port (modem, + mm_port_probe_peek_port (probe), + ptype, + pflags, + error); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x106c, 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_PANTECH, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_CUSTOM_AT_PROBE, custom_at_probe, + NULL)); +} + +static void +mm_plugin_pantech_init (MMPluginPantech *self) +{ +} + +static void +mm_plugin_pantech_class_init (MMPluginPantechClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = grab_port; +} diff --git a/src/plugins/pantech/mm-plugin-pantech.h b/src/plugins/pantech/mm-plugin-pantech.h new file mode 100644 index 00000000..fdbdd9ea --- /dev/null +++ b/src/plugins/pantech/mm-plugin-pantech.h @@ -0,0 +1,40 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_PANTECH_H +#define MM_PLUGIN_PANTECH_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_PANTECH (mm_plugin_pantech_get_type ()) +#define MM_PLUGIN_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_PANTECH, MMPluginPantech)) +#define MM_PLUGIN_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_PANTECH, MMPluginPantechClass)) +#define MM_IS_PLUGIN_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_PANTECH)) +#define MM_IS_PLUGIN_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_PANTECH)) +#define MM_PLUGIN_PANTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_PANTECH, MMPluginPantechClass)) + +typedef struct { + MMPlugin parent; +} MMPluginPantech; + +typedef struct { + MMPluginClass parent; +} MMPluginPantechClass; + +GType mm_plugin_pantech_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_PANTECH_H */ diff --git a/src/plugins/pantech/mm-sim-pantech.c b/src/plugins/pantech/mm-sim-pantech.c new file mode 100644 index 00000000..33414572 --- /dev/null +++ b/src/plugins/pantech/mm-sim-pantech.c @@ -0,0 +1,87 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-sim-pantech.h" + +G_DEFINE_TYPE (MMSimPantech, mm_sim_pantech, MM_TYPE_BASE_SIM) + +/*****************************************************************************/ + +MMBaseSim * +mm_sim_pantech_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *sim; + + source = g_async_result_get_source_object (res); + sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!sim) + return NULL; + + /* Only export valid SIMs */ + mm_base_sim_export (MM_BASE_SIM (sim)); + + return MM_BASE_SIM (sim); +} + +void +mm_sim_pantech_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (MM_TYPE_SIM_PANTECH, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_SIM_MODEM, modem, + "active", TRUE, /* by default always active */ + NULL); +} + +static void +mm_sim_pantech_init (MMSimPantech *self) +{ +} + +static void +mm_sim_pantech_class_init (MMSimPantechClass *klass) +{ + MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass); + + /* Skip querying most SIM card info, +CRSM just shoots the Pantech modems + * (at least the UMW190) in the head */ + base_sim_class->load_sim_identifier = NULL; + base_sim_class->load_sim_identifier_finish = NULL; + base_sim_class->load_operator_identifier = NULL; + base_sim_class->load_operator_identifier_finish = NULL; + base_sim_class->load_operator_name = NULL; + base_sim_class->load_operator_name_finish = NULL; +} diff --git a/src/plugins/pantech/mm-sim-pantech.h b/src/plugins/pantech/mm-sim-pantech.h new file mode 100644 index 00000000..8d227645 --- /dev/null +++ b/src/plugins/pantech/mm-sim-pantech.h @@ -0,0 +1,51 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_SIM_PANTECH_H +#define MM_SIM_PANTECH_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-sim.h" + +#define MM_TYPE_SIM_PANTECH (mm_sim_pantech_get_type ()) +#define MM_SIM_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_PANTECH, MMSimPantech)) +#define MM_SIM_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_PANTECH, MMSimPantechClass)) +#define MM_IS_SIM_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_PANTECH)) +#define MM_IS_SIM_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_PANTECH)) +#define MM_SIM_PANTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_PANTECH, MMSimPantechClass)) + +typedef struct _MMSimPantech MMSimPantech; +typedef struct _MMSimPantechClass MMSimPantechClass; + +struct _MMSimPantech { + MMBaseSim parent; +}; + +struct _MMSimPantechClass { + MMBaseSimClass parent; +}; + +GType mm_sim_pantech_get_type (void); + +void mm_sim_pantech_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_sim_pantech_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_SIM_PANTECH_H */ diff --git a/src/plugins/qcom-soc/77-mm-qcom-soc.rules b/src/plugins/qcom-soc/77-mm-qcom-soc.rules new file mode 100644 index 00000000..9719f96f --- /dev/null +++ b/src/plugins/qcom-soc/77-mm-qcom-soc.rules @@ -0,0 +1,40 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_qcom_soc_end" + +# Process only known wwan, net and rpmsg ports +SUBSYSTEM=="net", DRIVERS=="bam-dmux", GOTO="mm_qcom_soc_process" +SUBSYSTEM=="net", DRIVERS=="ipa", GOTO="mm_qcom_soc_process" +SUBSYSTEM=="wwan", DRIVERS=="qcom-q6v5-mss", GOTO="mm_qcom_soc_process" +SUBSYSTEM=="rpmsg", DRIVERS=="qcom-q6v5-mss", GOTO="mm_qcom_soc_process" +GOTO="mm_qcom_soc_end" + +LABEL="mm_qcom_soc_process" + +# Flag the port as being part of the SoC +ENV{ID_MM_QCOM_SOC}="1" + +# +# Add a common physdev UID to all ports in the Qualcomm SoC, so that they +# are all bound together to the same modem object. +# +# The MSM8916, MSM8974, .... Qualcomm SoCs use the combination of RPMSG/WWAN +# based control ports plus BAM-DMUX based network ports. +# +ENV{ID_MM_PHYSDEV_UID}="qcom-soc" + +# port type hints for the rpmsgexport-ed ports +SUBSYSTEM=="rpmsg", ATTR{name}=="DATA*", ATTR{name}=="*_CNTL", ENV{ID_MM_PORT_TYPE_QMI}="1" +SUBSYSTEM=="rpmsg", ATTR{name}=="DATA*", ATTR{name}!="*_CNTL", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# ignore every other port without explicit hints +SUBSYSTEM=="rpmsg", ENV{ID_MM_PORT_TYPE_QMI}!="1", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}!="1", ENV{ID_MM_PORT_IGNORE}="1" + +# explicitly ignore ports intended for USB tethering (DATA40, DATA40_CNTL) +SUBSYSTEM=="rpmsg", ATTR{name}=="DATA40*", ENV{ID_MM_PORT_IGNORE}="1" +KERNEL=="rmnet_usb*", ENV{ID_MM_PORT_IGNORE}="1" + +# flag all rpmsg ports under this plugin as candidate +KERNEL=="rpmsg*", SUBSYSTEM=="rpmsg", ENV{ID_MM_CANDIDATE}="1" + +LABEL="mm_qcom_soc_end" diff --git a/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.c b/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.c new file mode 100644 index 00000000..21d62c12 --- /dev/null +++ b/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.c @@ -0,0 +1,176 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log.h" +#include "mm-iface-modem.h" +#include "mm-broadband-modem-qmi-qcom-soc.h" + +G_DEFINE_TYPE (MMBroadbandModemQmiQcomSoc, mm_broadband_modem_qmi_qcom_soc, MM_TYPE_BROADBAND_MODEM_QMI) + +/*****************************************************************************/ + +static const QmiSioPort sio_port_per_port_number[] = { + QMI_SIO_PORT_A2_MUX_RMNET0, + QMI_SIO_PORT_A2_MUX_RMNET1, + QMI_SIO_PORT_A2_MUX_RMNET2, + QMI_SIO_PORT_A2_MUX_RMNET3, + QMI_SIO_PORT_A2_MUX_RMNET4, + QMI_SIO_PORT_A2_MUX_RMNET5, + QMI_SIO_PORT_A2_MUX_RMNET6, + QMI_SIO_PORT_A2_MUX_RMNET7 +}; + +static MMPortQmi * +peek_port_qmi_for_data_bam_dmux (MMBroadbandModemQmi *self, + MMPort *data, + MMQmiDataEndpoint *out_endpoint, + GError **error) +{ + MMPortQmi *found = NULL; + MMKernelDevice *net_port; + gint net_port_number; + + net_port = mm_port_peek_kernel_device (data); + + /* The dev_port notified by the bam-dmux driver indicates which SIO port we should be using */ + net_port_number = mm_kernel_device_get_attribute_as_int (net_port, "dev_port"); + if (net_port_number < 0 || net_port_number >= (gint) G_N_ELEMENTS (sio_port_per_port_number)) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "Couldn't find SIO port number for 'net/%s'", + mm_port_get_device (data)); + return NULL; + } + + /* Find one QMI port, we don't care which one */ + found = mm_broadband_modem_qmi_peek_port_qmi (self); + + if (!found) + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "Couldn't find any QMI port for 'net/%s'", + mm_port_get_device (data)); + else if (out_endpoint) { + /* WDS Bind (Mux) Data Port must be called with the correct endpoint + * interface number/SIO port to make multiplexing work with BAM-DMUX */ + out_endpoint->type = QMI_DATA_ENDPOINT_TYPE_BAM_DMUX; + out_endpoint->interface_number = net_port_number; + out_endpoint->sio_port = sio_port_per_port_number[net_port_number]; + } + + return found; +} + +static MMPortQmi * +peek_port_qmi_for_data_ipa (MMBroadbandModemQmi *self, + MMPort *data, + MMQmiDataEndpoint *out_endpoint, + GError **error) +{ + MMPortQmi *found = NULL; + + /* when using IPA, we have a main network interface that will be multiplexed + * to create link interfaces. We can assume any of the available QMI ports is + * able to manage that. */ + + found = mm_broadband_modem_qmi_peek_port_qmi (self); + + if (!found) + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "Couldn't find any QMI port for 'net/%s'", + mm_port_get_device (data)); + else if (out_endpoint) + mm_port_qmi_get_endpoint_info (found, out_endpoint); + + return found; +} + +static MMPortQmi * +peek_port_qmi_for_data (MMBroadbandModemQmi *self, + MMPort *data, + MMQmiDataEndpoint *out_endpoint, + GError **error) +{ + MMKernelDevice *net_port; + const gchar *net_port_driver; + + g_assert (MM_IS_BROADBAND_MODEM_QMI (self)); + g_assert (mm_port_get_subsys (data) == MM_PORT_SUBSYS_NET); + + net_port = mm_port_peek_kernel_device (data); + net_port_driver = mm_kernel_device_get_driver (net_port); + + if (g_strcmp0 (net_port_driver, "ipa") == 0) + return peek_port_qmi_for_data_ipa (self, data, out_endpoint, error); + + if (g_strcmp0 (net_port_driver, "bam-dmux") == 0) + return peek_port_qmi_for_data_bam_dmux (self, data, out_endpoint, error); + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unsupported QMI kernel driver for 'net/%s': %s", + mm_port_get_device (data), + net_port_driver); + return NULL; +} + +/*****************************************************************************/ + +MMBroadbandModemQmiQcomSoc * +mm_broadband_modem_qmi_qcom_soc_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* QMI bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_qmi_qcom_soc_init (MMBroadbandModemQmiQcomSoc *self) +{ +} + +static void +mm_broadband_modem_qmi_qcom_soc_class_init (MMBroadbandModemQmiQcomSocClass *klass) +{ + MMBroadbandModemQmiClass *broadband_modem_qmi_class = MM_BROADBAND_MODEM_QMI_CLASS (klass); + + broadband_modem_qmi_class->peek_port_qmi_for_data = peek_port_qmi_for_data; +} diff --git a/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.h b/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.h new file mode 100644 index 00000000..92c37beb --- /dev/null +++ b/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.h @@ -0,0 +1,49 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_QMI_QCOM_SOC_H +#define MM_BROADBAND_MODEM_QMI_QCOM_SOC_H + +#include "mm-broadband-modem-qmi.h" + +#define MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC (mm_broadband_modem_qmi_qcom_soc_get_type ()) +#define MM_BROADBAND_MODEM_QMI_QCOM_SOC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC, MMBroadbandModemQmiQcomSoc)) +#define MM_BROADBAND_MODEM_QMI_QCOM_SOC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC, MMBroadbandModemQmiQcomSocClass)) +#define MM_IS_BROADBAND_MODEM_QMI_QCOM_SOC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC)) +#define MM_IS_BROADBAND_MODEM_QMI_QCOM_SOC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC)) +#define MM_BROADBAND_MODEM_QMI_QCOM_SOC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC, MMBroadbandModemQmiQcomSocClass)) + +typedef struct _MMBroadbandModemQmiQcomSoc MMBroadbandModemQmiQcomSoc; +typedef struct _MMBroadbandModemQmiQcomSocClass MMBroadbandModemQmiQcomSocClass; +typedef struct _MMBroadbandModemQmiQcomSocPrivate MMBroadbandModemQmiQcomSocPrivate; + +struct _MMBroadbandModemQmiQcomSoc { + MMBroadbandModemQmi parent; + MMBroadbandModemQmiQcomSocPrivate *priv; +}; + +struct _MMBroadbandModemQmiQcomSocClass{ + MMBroadbandModemQmiClass parent; +}; + +GType mm_broadband_modem_qmi_qcom_soc_get_type (void); + +MMBroadbandModemQmiQcomSoc *mm_broadband_modem_qmi_qcom_soc_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_QMI_QCOM_SOC_H */ diff --git a/src/plugins/qcom-soc/mm-plugin-qcom-soc.c b/src/plugins/qcom-soc/mm-plugin-qcom-soc.c new file mode 100644 index 00000000..ae844dd6 --- /dev/null +++ b/src/plugins/qcom-soc/mm-plugin-qcom-soc.c @@ -0,0 +1,98 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> +#include <termios.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <getopt.h> +#include <time.h> + +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-qcom-soc.h" +#include "mm-broadband-modem-qmi-qcom-soc.h" +#include "mm-log-object.h" + +G_DEFINE_TYPE (MMPluginQcomSoc, mm_plugin_qcom_soc, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + if (!mm_port_probe_list_has_qmi_port (probes)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unsupported device: at least a QMI port is required"); + return NULL; + } + + mm_obj_dbg (self, "Qualcomm SoC modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_qcom_soc_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "wwan", "rpmsg", "net", "qrtr", NULL }; + static const gchar *udev_tags[] = { + "ID_MM_QCOM_SOC", + NULL + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_QCOM_SOC, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags, + NULL)); +} + +static void +mm_plugin_qcom_soc_init (MMPluginQcomSoc *self) +{ +} + +static void +mm_plugin_qcom_soc_class_init (MMPluginQcomSocClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/qcom-soc/mm-plugin-qcom-soc.h b/src/plugins/qcom-soc/mm-plugin-qcom-soc.h new file mode 100644 index 00000000..54da154f --- /dev/null +++ b/src/plugins/qcom-soc/mm-plugin-qcom-soc.h @@ -0,0 +1,40 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_QCOM_SOC_H +#define MM_PLUGIN_QCOM_SOC_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_QCOM_SOC (mm_plugin_qcom_soc_get_type ()) +#define MM_PLUGIN_QCOM_SOC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_QCOM_SOC, MMPluginQcomSoc)) +#define MM_PLUGIN_QCOM_SOC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_QCOM_SOC, MMPluginQcomSocClass)) +#define MM_IS_PLUGIN_QCOM_SOC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_QCOM_SOC)) +#define MM_IS_PLUGIN_QCOM_SOC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_QCOM_SOC)) +#define MM_PLUGIN_QCOM_SOC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_QCOM_SOC, MMPluginQcomSocClass)) + +typedef struct { + MMPlugin parent; +} MMPluginQcomSoc; + +typedef struct { + MMPluginClass parent; +} MMPluginQcomSocClass; + +GType mm_plugin_qcom_soc_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_QCOM_SOC_H */ diff --git a/src/plugins/quectel/77-mm-quectel-port-types.rules b/src/plugins/quectel/77-mm-quectel-port-types.rules new file mode 100644 index 00000000..08564161 --- /dev/null +++ b/src/plugins/quectel/77-mm-quectel-port-types.rules @@ -0,0 +1,104 @@ +# do not edit this file, it will be overwritten on update +ACTION!="add|change|move|bind", GOTO="mm_quectel_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c7c", GOTO="mm_quectel_usb" +SUBSYSTEMS=="pci", ATTRS{vendor}=="0x1eac", GOTO="mm_quectel_pci" +GOTO="mm_quectel_end" + +LABEL="mm_quectel_usb" + +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Quectel EG06 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel EG91 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel EG95 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0195", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0195", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0195", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0195", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel BG96 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0296", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0296", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0296", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0296", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel EC25/EG25 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel RM500 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0800", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0800", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0800", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0800", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel EM05-G variants with Sahara-Firehose support: +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="030a", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="030a", ENV{ID_MM_QUECTEL_SAHARA}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="030c", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="030c", ENV{ID_MM_QUECTEL_SAHARA}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0311", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0311", ENV{ID_MM_QUECTEL_SAHARA}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0313", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0313", ENV{ID_MM_QUECTEL_SAHARA}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0314", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0314", ENV{ID_MM_QUECTEL_SAHARA}="1" + +# Quectel EM05-CN variants with Sahara-Firehose support +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0310", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0310", ENV{ID_MM_QUECTEL_SAHARA}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0312", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0312", ENV{ID_MM_QUECTEL_SAHARA}="1" + +# Quectel EM05-CE with Sahara-Firehose support +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0127", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0127", ENV{ID_MM_QUECTEL_SAHARA}="1" + +GOTO="mm_quectel_end" + +LABEL="mm_quectel_pci" + +# Quectel EM120 and EM160 with firehose support +ATTRS{vendor}=="0x1eac", ATTRS{device}=="0x1001", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{vendor}=="0x1eac", ATTRS{device}=="0x1002", ENV{ID_MM_QUECTEL_FIREHOSE}="1" + +# Quectel RM520 with firehose support +ATTRS{vendor}=="0x1eac", ATTRS{device}=="0x1004", ENV{ID_MM_QUECTEL_FIREHOSE}="1" + +LABEL="mm_quectel_end" diff --git a/src/plugins/quectel/mm-broadband-modem-mbim-quectel.c b/src/plugins/quectel/mm-broadband-modem-mbim-quectel.c new file mode 100644 index 00000000..0ab40610 --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-mbim-quectel.c @@ -0,0 +1,88 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + * Copyright (C) 2021 Ivan Mikhanchuk <ivan.mikhanchuk@quectel.com> + */ + +#include <config.h> + +#include "mm-base-modem-at.h" +#include "mm-log-object.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-firmware.h" +#include "mm-iface-modem-time.h" +#include "mm-shared-quectel.h" +#include "mm-modem-helpers-quectel.h" +#include "mm-broadband-modem-mbim-quectel.h" + +static void iface_modem_firmware_init (MMIfaceModemFirmware *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); +static void shared_quectel_init (MMSharedQuectel *iface); + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimQuectel, mm_broadband_modem_mbim_quectel, MM_TYPE_BROADBAND_MODEM_MBIM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_QUECTEL, shared_quectel_init)) + +/*****************************************************************************/ + +MMBroadbandModemMbimQuectel * +mm_broadband_modem_mbim_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* include carrier information */ + MM_IFACE_MODEM_FIRMWARE_IGNORE_CARRIER, FALSE, + /* MBIM bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_mbim_quectel_init (MMBroadbandModemMbimQuectel *self) +{ +} + +static void +iface_modem_firmware_init (MMIfaceModemFirmware *iface) +{ + iface->load_update_settings = mm_shared_quectel_firmware_load_update_settings; + iface->load_update_settings_finish = mm_shared_quectel_firmware_load_update_settings_finish; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface->check_support = mm_shared_quectel_time_check_support; + iface->check_support_finish = mm_shared_quectel_time_check_support_finish; +} + +static void +shared_quectel_init (MMSharedQuectel *iface) +{ +} + +static void +mm_broadband_modem_mbim_quectel_class_init (MMBroadbandModemMbimQuectelClass *klass) +{ +} diff --git a/src/plugins/quectel/mm-broadband-modem-mbim-quectel.h b/src/plugins/quectel/mm-broadband-modem-mbim-quectel.h new file mode 100644 index 00000000..0d0c2b95 --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-mbim-quectel.h @@ -0,0 +1,48 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + * Copyright (C) 2021 Ivan Mikhanchuk <ivan.mikhanchuk@quectel.com> + */ + +#ifndef MM_BROADBAND_MODEM_MBIM_QUECTEL_H +#define MM_BROADBAND_MODEM_MBIM_QUECTEL_H + +#include "mm-broadband-modem-mbim.h" + +#define MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL (mm_broadband_modem_mbim_quectel_get_type ()) +#define MM_BROADBAND_MODEM_MBIM_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL, MMBroadbandModemMbimQuectel)) +#define MM_BROADBAND_MODEM_MBIM_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL, MMBroadbandModemMbimQuectelClass)) +#define MM_IS_BROADBAND_MODEM_MBIM_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL)) +#define MM_IS_BROADBAND_MODEM_MBIM_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL)) +#define MM_BROADBAND_MODEM_MBIM_QUECTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL, MMBroadbandModemMbimQuectelClass)) + +typedef struct _MMBroadbandModemMbimQuectel MMBroadbandModemMbimQuectel; +typedef struct _MMBroadbandModemMbimQuectelClass MMBroadbandModemMbimQuectelClass; + +struct _MMBroadbandModemMbimQuectel { + MMBroadbandModemMbim parent; +}; + +struct _MMBroadbandModemMbimQuectelClass{ + MMBroadbandModemMbimClass parent; +}; + +GType mm_broadband_modem_mbim_quectel_get_type (void); + +MMBroadbandModemMbimQuectel *mm_broadband_modem_mbim_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_MBIM_QUECTEL_H */ diff --git a/src/plugins/quectel/mm-broadband-modem-qmi-quectel.c b/src/plugins/quectel/mm-broadband-modem-qmi-quectel.c new file mode 100644 index 00000000..a4ccbfc9 --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-qmi-quectel.c @@ -0,0 +1,138 @@ +/* -*- 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) 2018-2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include "mm-broadband-modem-qmi-quectel.h" +#include "mm-iface-modem-firmware.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-time.h" +#include "mm-shared-quectel.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_firmware_init (MMIfaceModemFirmware *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); +static void shared_quectel_init (MMSharedQuectel *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiQuectel, mm_broadband_modem_qmi_quectel, MM_TYPE_BROADBAND_MODEM_QMI, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_QUECTEL, shared_quectel_init)) + +/*****************************************************************************/ + +MMBroadbandModemQmiQuectel * +mm_broadband_modem_qmi_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* exclude carrier information */ + MM_IFACE_MODEM_FIRMWARE_IGNORE_CARRIER, TRUE, + /* QMI bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_qmi_quectel_init (MMBroadbandModemQmiQuectel *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->setup_sim_hot_swap = mm_shared_quectel_setup_sim_hot_swap; + iface->setup_sim_hot_swap_finish = mm_shared_quectel_setup_sim_hot_swap_finish; + iface->cleanup_sim_hot_swap = mm_shared_quectel_cleanup_sim_hot_swap; +} + +static MMIfaceModem * +peek_parent_modem_interface (MMSharedQuectel *self) +{ + return iface_modem_parent; +} + +static MMBroadbandModemClass * +peek_parent_broadband_modem_class (MMSharedQuectel *self) +{ + return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_qmi_quectel_parent_class); +} + +static void +iface_modem_firmware_init (MMIfaceModemFirmware *iface) +{ + iface->load_update_settings = mm_shared_quectel_firmware_load_update_settings; + iface->load_update_settings_finish = mm_shared_quectel_firmware_load_update_settings_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_quectel_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_quectel_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_quectel_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_quectel_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_quectel_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_quectel_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_modem_location_interface (MMSharedQuectel *self) +{ + return iface_modem_location_parent; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface->check_support = mm_shared_quectel_time_check_support; + iface->check_support_finish = mm_shared_quectel_time_check_support_finish; +} + +static void +shared_quectel_init (MMSharedQuectel *iface) +{ + iface->peek_parent_modem_interface = peek_parent_modem_interface; + iface->peek_parent_modem_location_interface = peek_parent_modem_location_interface; + iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class; +} + +static void +mm_broadband_modem_qmi_quectel_class_init (MMBroadbandModemQmiQuectelClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = mm_shared_quectel_setup_ports; +} diff --git a/src/plugins/quectel/mm-broadband-modem-qmi-quectel.h b/src/plugins/quectel/mm-broadband-modem-qmi-quectel.h new file mode 100644 index 00000000..f1580f0e --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-qmi-quectel.h @@ -0,0 +1,47 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_QMI_QUECTEL_H +#define MM_BROADBAND_MODEM_QMI_QUECTEL_H + +#include "mm-broadband-modem-qmi.h" + +#define MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL (mm_broadband_modem_qmi_quectel_get_type ()) +#define MM_BROADBAND_MODEM_QMI_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL, MMBroadbandModemQmiQuectel)) +#define MM_BROADBAND_MODEM_QMI_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL, MMBroadbandModemQmiQuectelClass)) +#define MM_IS_BROADBAND_MODEM_QMI_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL)) +#define MM_IS_BROADBAND_MODEM_QMI_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL)) +#define MM_BROADBAND_MODEM_QMI_QUECTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL, MMBroadbandModemQmiQuectelClass)) + +typedef struct _MMBroadbandModemQmiQuectel MMBroadbandModemQmiQuectel; +typedef struct _MMBroadbandModemQmiQuectelClass MMBroadbandModemQmiQuectelClass; + +struct _MMBroadbandModemQmiQuectel { + MMBroadbandModemQmi parent; +}; + +struct _MMBroadbandModemQmiQuectelClass{ + MMBroadbandModemQmiClass parent; +}; + +GType mm_broadband_modem_qmi_quectel_get_type (void); + +MMBroadbandModemQmiQuectel *mm_broadband_modem_qmi_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_QMI_QUECTEL_H */ diff --git a/src/plugins/quectel/mm-broadband-modem-quectel.c b/src/plugins/quectel/mm-broadband-modem-quectel.c new file mode 100644 index 00000000..ad66b783 --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-quectel.c @@ -0,0 +1,136 @@ +/* -*- 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) 2018-2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include "mm-broadband-modem-quectel.h" +#include "mm-iface-modem-firmware.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-time.h" +#include "mm-shared-quectel.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_firmware_init (MMIfaceModemFirmware *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); +static void shared_quectel_init (MMSharedQuectel *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQuectel, mm_broadband_modem_quectel, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_QUECTEL, shared_quectel_init)) + +/*****************************************************************************/ + +MMBroadbandModemQuectel * +mm_broadband_modem_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_QUECTEL, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_quectel_init (MMBroadbandModemQuectel *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->setup_sim_hot_swap = mm_shared_quectel_setup_sim_hot_swap; + iface->setup_sim_hot_swap_finish = mm_shared_quectel_setup_sim_hot_swap_finish; + iface->cleanup_sim_hot_swap = mm_shared_quectel_cleanup_sim_hot_swap; +} + +static MMIfaceModem * +peek_parent_modem_interface (MMSharedQuectel *self) +{ + return iface_modem_parent; +} + +static MMBroadbandModemClass * +peek_parent_broadband_modem_class (MMSharedQuectel *self) +{ + return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_quectel_parent_class); +} + +static void +iface_modem_firmware_init (MMIfaceModemFirmware *iface) +{ + iface->load_update_settings = mm_shared_quectel_firmware_load_update_settings; + iface->load_update_settings_finish = mm_shared_quectel_firmware_load_update_settings_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_quectel_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_quectel_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_quectel_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_quectel_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_quectel_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_quectel_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_modem_location_interface (MMSharedQuectel *self) +{ + return iface_modem_location_parent; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface->check_support = mm_shared_quectel_time_check_support; + iface->check_support_finish = mm_shared_quectel_time_check_support_finish; +} + +static void +shared_quectel_init (MMSharedQuectel *iface) +{ + iface->peek_parent_modem_interface = peek_parent_modem_interface; + iface->peek_parent_modem_location_interface = peek_parent_modem_location_interface; + iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class; +} + +static void +mm_broadband_modem_quectel_class_init (MMBroadbandModemQuectelClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = mm_shared_quectel_setup_ports; +} diff --git a/src/plugins/quectel/mm-broadband-modem-quectel.h b/src/plugins/quectel/mm-broadband-modem-quectel.h new file mode 100644 index 00000000..bf4ef7a7 --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-quectel.h @@ -0,0 +1,47 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_QUECTEL_H +#define MM_BROADBAND_MODEM_QUECTEL_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_QUECTEL (mm_broadband_modem_quectel_get_type ()) +#define MM_BROADBAND_MODEM_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QUECTEL, MMBroadbandModemQuectel)) +#define MM_BROADBAND_MODEM_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QUECTEL, MMBroadbandModemQuectelClass)) +#define MM_IS_BROADBAND_MODEM_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QUECTEL)) +#define MM_IS_BROADBAND_MODEM_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QUECTEL)) +#define MM_BROADBAND_MODEM_QUECTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QUECTEL, MMBroadbandModemQuectelClass)) + +typedef struct _MMBroadbandModemQuectel MMBroadbandModemQuectel; +typedef struct _MMBroadbandModemQuectelClass MMBroadbandModemQuectelClass; + +struct _MMBroadbandModemQuectel { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemQuectelClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_quectel_get_type (void); + +MMBroadbandModemQuectel *mm_broadband_modem_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_QUECTEL_H */ diff --git a/src/plugins/quectel/mm-modem-helpers-quectel.c b/src/plugins/quectel/mm-modem-helpers-quectel.c new file mode 100644 index 00000000..262d9794 --- /dev/null +++ b/src/plugins/quectel/mm-modem-helpers-quectel.c @@ -0,0 +1,91 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-quectel.h" + +gboolean +mm_quectel_parse_ctzu_test_response (const gchar *response, + gpointer log_object, + gboolean *supports_disable, + gboolean *supports_enable, + gboolean *supports_enable_update_rtc, + GError **error) +{ + g_auto(GStrv) split = NULL; + g_autoptr(GArray) modes = NULL; + guint i; + + /* + * Response may be: + * - +CTZU: (0,1) + * - +CTZU: (0,1,3) + */ + +#define N_EXPECTED_GROUPS 1 + + split = mm_split_string_groups (mm_strip_tag (response, "+CTZU:")); + if (!split) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't split the +CTZU test response in groups"); + return FALSE; + } + + if (g_strv_length (split) != N_EXPECTED_GROUPS) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Cannot parse +CTZU test response: invalid number of groups (%u != %u)", + g_strv_length (split), N_EXPECTED_GROUPS); + return FALSE; + } + + modes = mm_parse_uint_list (split[0], error); + if (!modes) { + g_prefix_error (error, "Failed to parse integer list in +CTZU test response: "); + return FALSE; + } + + *supports_disable = FALSE; + *supports_enable = FALSE; + *supports_enable_update_rtc = FALSE; + + for (i = 0; i < modes->len; i++) { + guint mode; + + mode = g_array_index (modes, guint, i); + switch (mode) { + case 0: + *supports_disable = TRUE; + break; + case 1: + *supports_enable = TRUE; + break; + case 3: + *supports_enable_update_rtc = TRUE; + break; + default: + mm_obj_dbg (log_object, "unknown +CTZU mode: %u", mode); + break; + } + } + + return TRUE; +} diff --git a/src/plugins/quectel/mm-modem-helpers-quectel.h b/src/plugins/quectel/mm-modem-helpers-quectel.h new file mode 100644 index 00000000..d4ec0eae --- /dev/null +++ b/src/plugins/quectel/mm-modem-helpers-quectel.h @@ -0,0 +1,32 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_MODEM_HELPERS_QUECTEL_H +#define MM_MODEM_HELPERS_QUECTEL_H + +#include <glib.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +gboolean mm_quectel_parse_ctzu_test_response (const gchar *response, + gpointer log_object, + gboolean *supports_disable, + gboolean *supports_enable, + gboolean *supports_enable_update_rtc, + GError **error); + +#endif /* MM_MODEM_HELPERS_QUECTEL_H */ diff --git a/src/plugins/quectel/mm-plugin-quectel.c b/src/plugins/quectel/mm-plugin-quectel.c new file mode 100644 index 00000000..80e1b74d --- /dev/null +++ b/src/plugins/quectel/mm-plugin-quectel.c @@ -0,0 +1,117 @@ +/* -*- 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) 2017-2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <stdlib.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-quectel.h" +#include "mm-broadband-modem-quectel.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi-quectel.h" +#endif + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#include "mm-broadband-modem-mbim-quectel.h" +#endif + +G_DEFINE_TYPE (MMPluginQuectel, mm_plugin_quectel, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Quectel modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_quectel_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered Quectel modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_quectel_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_quectel_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", "wwan", NULL }; + static const gchar *vendor_strings[] = { "quectel", NULL }; + static const guint16 vendor_ids[] = { + 0x2c7c, /* usb vid */ + 0x1eac, /* pci vid */ + 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_QUECTEL, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_REQUIRED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + NULL)); +} + +static void +mm_plugin_quectel_init (MMPluginQuectel *self) +{ +} + +static void +mm_plugin_quectel_class_init (MMPluginQuectelClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/quectel/mm-plugin-quectel.h b/src/plugins/quectel/mm-plugin-quectel.h new file mode 100644 index 00000000..ec888821 --- /dev/null +++ b/src/plugins/quectel/mm-plugin-quectel.h @@ -0,0 +1,40 @@ +/* -*- 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) 2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_QUECTEL_H +#define MM_PLUGIN_QUECTEL_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_QUECTEL (mm_plugin_quectel_get_type ()) +#define MM_PLUGIN_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_QUECTEL, MMPluginQuectel)) +#define MM_PLUGIN_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_QUECTEL, MMPluginQuectelClass)) +#define MM_IS_PLUGIN_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_QUECTEL)) +#define MM_IS_PLUGIN_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_QUECTEL)) +#define MM_PLUGIN_QUECTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_QUECTEL, MMPluginQuectelClass)) + +typedef struct { + MMPlugin parent; +} MMPluginQuectel; + +typedef struct { + MMPluginClass parent; +} MMPluginQuectelClass; + +GType mm_plugin_quectel_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_QUECTEL_H */ diff --git a/src/plugins/quectel/mm-shared-quectel.c b/src/plugins/quectel/mm-shared-quectel.c new file mode 100644 index 00000000..47d7cd33 --- /dev/null +++ b/src/plugins/quectel/mm-shared-quectel.c @@ -0,0 +1,1039 @@ +/* -*- 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) 2018-2020 Aleksander Morgado <aleksander@aleksander.es> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. + */ + +#include <config.h> + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-firmware.h" +#include "mm-iface-modem-location.h" +#include "mm-base-modem.h" +#include "mm-base-modem-at.h" +#include "mm-shared-quectel.h" +#include "mm-modem-helpers-quectel.h" + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#endif + +/*****************************************************************************/ +/* Private context */ + +#define PRIVATE_TAG "shared-quectel-private-tag" +static GQuark private_quark; + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED, +} FeatureSupport; + +typedef struct { + MMBroadbandModemClass *broadband_modem_class_parent; + MMIfaceModem *iface_modem_parent; + MMIfaceModemLocation *iface_modem_location_parent; + MMModemLocationSource provided_sources; + MMModemLocationSource enabled_sources; + FeatureSupport qgps_supported; + GRegex *qgpsurc_regex; + GRegex *qlwurc_regex; + GRegex *rdy_regex; +} Private; + +static void +private_free (Private *priv) +{ + g_regex_unref (priv->qgpsurc_regex); + g_regex_unref (priv->qlwurc_regex); + g_regex_unref (priv->rdy_regex); + g_slice_free (Private, priv); +} + +static Private * +get_private (MMSharedQuectel *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); + + priv->provided_sources = MM_MODEM_LOCATION_SOURCE_NONE; + priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE; + priv->qgps_supported = FEATURE_SUPPORT_UNKNOWN; + priv->qgpsurc_regex = g_regex_new ("\\r\\n\\+QGPSURC:.*", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + priv->qlwurc_regex = g_regex_new ("\\r\\n\\+QLWURC:.*", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + priv->rdy_regex = g_regex_new ("\\r\\nRDY", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + g_assert (priv->qgpsurc_regex); + g_assert (priv->qlwurc_regex); + g_assert (priv->rdy_regex); + + g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_broadband_modem_class); + priv->broadband_modem_class_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_broadband_modem_class (self); + + g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_location_interface); + priv->iface_modem_location_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_location_interface (self); + + g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_interface); + priv->iface_modem_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_interface (self); + + g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); + } + return priv; +} + +/*****************************************************************************/ +/* RDY unsolicited event handler */ + +static void +rdy_handler (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModem *self) +{ + /* The RDY URC indicates a modem reset that may or may not go hand-in-hand + * with USB re-enumeration. For the latter case, we must make sure to + * re-synchronize modem and ModemManager states by re-probing. + */ + mm_obj_warn (self, "modem reset detected, triggering reprobe"); + mm_base_modem_set_reprobe (MM_BASE_MODEM (self), TRUE); + mm_base_modem_set_valid (MM_BASE_MODEM (self), FALSE); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +void +mm_shared_quectel_setup_ports (MMBroadbandModem *self) +{ + Private *priv; + MMPortSerialAt *ports[2]; + guint i; + + priv = get_private (MM_SHARED_QUECTEL (self)); + g_assert (priv->broadband_modem_class_parent); + g_assert (priv->broadband_modem_class_parent->setup_ports); + + /* Parent setup always first */ + priv->broadband_modem_class_parent->setup_ports (self); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable/disable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* Ignore +QGPSURC */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + priv->qgpsurc_regex, + NULL, NULL, NULL); + + /* Ignore +QLWURC */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + priv->qlwurc_regex, + NULL, NULL, NULL); + + /* Handle RDY */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + priv->rdy_regex, + (MMPortSerialAtUnsolicitedMsgFn)rdy_handler, + self, + NULL); + } +} + +/*****************************************************************************/ +/* Firmware update settings loading (Firmware interface) */ + +MMFirmwareUpdateSettings * +mm_shared_quectel_firmware_load_update_settings_finish (MMIfaceModemFirmware *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static gboolean +quectel_is_sahara_supported (MMBaseModem *modem, + MMPort *port) +{ + return mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_QUECTEL_SAHARA"); +} + +static gboolean +quectel_is_firehose_supported (MMBaseModem *modem, + MMPort *port) +{ + return mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_QUECTEL_FIREHOSE"); +} + +static MMModemFirmwareUpdateMethod +quectel_get_firmware_update_methods (MMBaseModem *modem, + MMPort *port) +{ + MMModemFirmwareUpdateMethod update_methods; + + update_methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE; + + if (quectel_is_firehose_supported (modem, port)) + update_methods |= MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE; + if (quectel_is_sahara_supported (modem, port)) + update_methods |= MM_MODEM_FIRMWARE_UPDATE_METHOD_SAHARA; + + return update_methods; +} + +static void +quectel_at_port_get_firmware_version_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMFirmwareUpdateSettings *update_settings; + const gchar *version; + + update_settings = g_task_get_task_data (task); + + version = mm_base_modem_at_command_finish (modem, res, NULL); + if (version) + mm_firmware_update_settings_set_version (update_settings, version); + + g_task_return_pointer (task, g_object_ref (update_settings), g_object_unref); + g_object_unref (task); +} + +#if defined WITH_MBIM +static void +quectel_mbim_port_get_firmware_version_ready (MbimDevice *device, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(MbimMessage) response = NULL; + guint32 version_id; + g_autofree gchar *version_str = NULL; + MMFirmwareUpdateSettings *update_settings; + + update_settings = g_task_get_task_data (task); + + response = mbim_device_command_finish (device, res, NULL); + if (response && mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, NULL) && + mbim_message_qdu_quectel_read_version_response_parse (response, &version_id, &version_str, NULL)) { + mm_firmware_update_settings_set_version (update_settings, version_str); + } + + g_task_return_pointer (task, g_object_ref (update_settings), g_object_unref); + g_object_unref (task); +} +#endif + +static void +qfastboot_test_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMFirmwareUpdateSettings *update_settings; + + update_settings = g_task_get_task_data (task); + + /* Set update method */ + if (mm_base_modem_at_command_finish (self, res, NULL)) { + mm_firmware_update_settings_set_method (update_settings, MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT); + mm_firmware_update_settings_set_fastboot_at (update_settings, "AT+QFASTBOOT"); + } else + mm_firmware_update_settings_set_method (update_settings, MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE); + + /* Fetch full firmware info */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+QGMR?", + 3, + FALSE, + (GAsyncReadyCallback) quectel_at_port_get_firmware_version_ready, + task); +} + +static void +quectel_at_port_get_firmware_revision_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMFirmwareUpdateSettings *update_settings; + MMModemFirmwareUpdateMethod update_methods; + const gchar *revision; + const gchar *name; + const gchar *id; + g_autoptr(GPtrArray) ids = NULL; + GError *error = NULL; + + update_settings = g_task_get_task_data (task); + update_methods = mm_firmware_update_settings_get_method (update_settings); + + /* Set device ids */ + ids = mm_iface_firmware_build_generic_device_ids (MM_IFACE_MODEM_FIRMWARE (self), &error); + if (error) { + mm_obj_warn (self, "failed to build generic device ids: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Add device id based on modem name */ + revision = mm_base_modem_at_command_finish (self, res, NULL); + if (revision && g_utf8_validate (revision, -1, NULL)) { + name = g_strndup (revision, 7); + mm_obj_dbg (self, "revision %s converted to modem name %s", revision, name); + id = (const gchar *) g_ptr_array_index (ids, 0); + g_ptr_array_insert (ids, 0, g_strdup_printf ("%s&NAME_%s", id, name)); + } + + mm_firmware_update_settings_set_device_ids (update_settings, (const gchar **)ids->pdata); + + /* Set update methods */ + if (update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) { + /* Fetch full firmware info */ + mm_base_modem_at_command (self, + "+QGMR?", + 3, + TRUE, + (GAsyncReadyCallback) quectel_at_port_get_firmware_version_ready, + task); + } else { + /* Check fastboot support */ + mm_base_modem_at_command (self, + "AT+QFASTBOOT=?", + 3, + TRUE, + (GAsyncReadyCallback) qfastboot_test_ready, + task); + } +} + +void +mm_shared_quectel_firmware_load_update_settings (MMIfaceModemFirmware *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMPortSerialAt *at_port; + MMModemFirmwareUpdateMethod update_methods; + MMFirmwareUpdateSettings *update_settings; +#if defined WITH_MBIM + MMPortMbim *mbim; +#endif + + task = g_task_new (self, NULL, callback, user_data); + + at_port = mm_base_modem_peek_best_at_port (MM_BASE_MODEM (self), NULL); + if (at_port) { + update_methods = quectel_get_firmware_update_methods (MM_BASE_MODEM (self), MM_PORT (at_port)); + update_settings = mm_firmware_update_settings_new (update_methods); + g_task_set_task_data (task, update_settings, g_object_unref); + + /* Fetch modem name */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CGMR", + 3, + TRUE, + (GAsyncReadyCallback) quectel_at_port_get_firmware_revision_ready, + task); + + return; + } + +#if defined WITH_MBIM + mbim = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self)); + if (mbim) { + g_autoptr(MbimMessage) message = NULL; + + update_methods = quectel_get_firmware_update_methods (MM_BASE_MODEM (self), MM_PORT (mbim)); + update_settings = mm_firmware_update_settings_new (update_methods); + + /* Fetch firmware info */ + g_task_set_task_data (task, update_settings, g_object_unref); + message = mbim_message_qdu_quectel_read_version_set_new (MBIM_QDU_QUECTEL_VERSION_TYPE_FW_BUILD_ID, NULL); + mbim_device_command (mm_port_mbim_peek_device (mbim), + message, + 5, + NULL, + (GAsyncReadyCallback) quectel_mbim_port_get_firmware_version_ready, + task); + return; + } +#endif + + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't find a port to fetch firmware info"); + g_object_unref (task); +} + +/*****************************************************************************/ +/* "+QUSIM: 1" URC is emitted by Quectel modems after the USIM has been + * (re)initialized. We register a handler for this URC and perform a check + * for SIM swap when it is encountered. The motivation for this is to detect + * M2M eUICC profile switches. According to SGP.02 chapter 3.2.1, the eUICC + * shall trigger a REFRESH operation with eUICC reset when a new profile is + * enabled. The +QUSIM URC appears after the eUICC has restarted and can act + * as a trigger for profile switch check. This should basically be handled + * the same as a physical SIM swap, so the existing SIM hot swap mechanism + * is used. + */ + +static void +quectel_qusim_check_for_sim_swap_ready (MMIfaceModem *self, + GAsyncResult *res) +{ + g_autoptr(GError) error = NULL; + + if (!MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap_finish (self, res, &error)) + mm_obj_warn (self, "couldn't check SIM swap: %s", error->message); + else + mm_obj_dbg (self, "check SIM swap completed"); +} + +static void +quectel_qusim_unsolicited_handler (MMPortSerialAt *port, + GMatchInfo *match_info, + MMIfaceModem *self) +{ + if (!MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap || + !MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap_finish) + return; + + mm_obj_dbg (self, "checking SIM swap"); + MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap ( + self, + NULL, + NULL, + (GAsyncReadyCallback)quectel_qusim_check_for_sim_swap_ready, + NULL); +} + +/*****************************************************************************/ +/* Setup SIM hot swap context (Modem interface) */ + +gboolean +mm_shared_quectel_setup_sim_hot_swap_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_sim_hot_swap_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + g_autoptr(GError) error = NULL; + + priv = get_private (MM_SHARED_QUECTEL (self)); + + if (!priv->iface_modem_parent->setup_sim_hot_swap_finish (self, res, &error)) + mm_obj_dbg (self, "additional SIM hot swap detection setup failed: %s", error->message); + + /* The +QUSIM based setup never fails, so we can safely return success here */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_quectel_setup_sim_hot_swap (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + MMPortSerialAt *ports[2]; + GTask *task; + guint i; + g_autoptr(GRegex) pattern = NULL; + g_autoptr(GError) error = NULL; + + priv = get_private (MM_SHARED_QUECTEL (self)); + + task = g_task_new (self, NULL, callback, user_data); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + pattern = g_regex_new ("\\+QUSIM:\\s*1\\r\\n", G_REGEX_RAW, 0, NULL); + g_assert (pattern); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (ports[i]) + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + pattern, + (MMPortSerialAtUnsolicitedMsgFn)quectel_qusim_unsolicited_handler, + self, + NULL); + } + + mm_obj_dbg (self, "+QUSIM detection set up"); + + if (!mm_broadband_modem_sim_hot_swap_ports_context_init (MM_BROADBAND_MODEM (self), &error)) + mm_obj_warn (self, "failed to initialize SIM hot swap ports context: %s", error->message); + + /* Now, if available, setup parent logic */ + if (priv->iface_modem_parent->setup_sim_hot_swap && + priv->iface_modem_parent->setup_sim_hot_swap_finish) { + priv->iface_modem_parent->setup_sim_hot_swap (self, + (GAsyncReadyCallback) parent_setup_sim_hot_swap_ready, + task); + return; + } + + /* Otherwise, we're done */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* SIM hot swap cleanup (Modem interface) */ + +void +mm_shared_quectel_cleanup_sim_hot_swap (MMIfaceModem *self) +{ + mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self)); +} + +/*****************************************************************************/ +/* GPS trace received */ + +static void +trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +/*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +MMModemLocationSource +mm_shared_quectel_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +probe_qgps_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMSharedQuectel *self; + Private *priv; + MMModemLocationSource sources; + + self = MM_SHARED_QUECTEL (g_task_get_source_object (task)); + priv = get_private (self); + + priv->qgps_supported = (!!mm_base_modem_at_command_finish (_self, res, NULL) ? + FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED); + + mm_obj_dbg (self, "GPS management with +QGPS is %ssupported", + priv->qgps_supported ? "" : "not "); + + /* Recover parent sources */ + sources = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + /* Only flag as provided those sources not already provided by the parent */ + if (priv->qgps_supported == FEATURE_SUPPORTED) { + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) + priv->provided_sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA; + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW)) + priv->provided_sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW; + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) + priv->provided_sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED; + + sources |= priv->provided_sources; + + /* Add handler for the NMEA traces in the GPS data port */ + mm_port_serial_gps_add_trace_handler (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)), + (MMPortSerialGpsTraceFn)trace_received, + self, + NULL); + } + + /* So we're done, complete */ + g_task_return_int (task, sources); + g_object_unref (task); +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + MMModemLocationSource sources; + GError *error = NULL; + + priv = get_private (MM_SHARED_QUECTEL (self)); + sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Now our own check. If we don't have any GPS port, we're done */ + if (!mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) { + mm_obj_dbg (self, "no GPS data port found: no GPS capabilities"); + g_task_return_int (task, sources); + g_object_unref (task); + return; + } + + /* Store parent supported sources in task data */ + g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL); + + /* Probe QGPS support */ + g_assert (priv->qgps_supported == FEATURE_SUPPORT_UNKNOWN); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+QGPS=?", + 3, + TRUE, /* cached */ + (GAsyncReadyCallback)probe_qgps_ready, + task); +} + +void +mm_shared_quectel_location_load_capabilities (MMIfaceModemLocation *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + task = g_task_new (_self, NULL, callback, user_data); + priv = get_private (MM_SHARED_QUECTEL (_self)); + + /* Chain up parent's setup */ + priv->iface_modem_location_parent->load_capabilities (_self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + task); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +/* NOTES: + * 1) "+QGPSCFG=\"nmeasrc\",1" will be necessary for getting location data + * without the nmea port. + * 2) may be necessary to set "+QGPSCFG=\"gpsnmeatype\". + * 3) QGPSXTRA=1 is necessary to support XTRA assistance data for + * faster GNSS location locks. + */ +static const MMBaseModemAtCommand gps_startup[] = { + { "+QGPSCFG=\"outport\",\"usbnmea\"", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, + { "+QGPS=1", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, + { "+QGPSXTRA=1", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, + { NULL } +}; + +gboolean +mm_shared_quectel_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_startup_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource source; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_QUECTEL (self)); + + mm_base_modem_at_sequence_finish (self, res, NULL, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + source = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + /* Check if the nmea/raw gps port exists and is available */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMPortSerialGps *gps_port; + + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (!gps_port || !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't open raw GPS serial port"); + } else { + /* GPS port was successfully opened */ + priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + } + } else { + /* No need to open GPS port */ + priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_QUECTEL (self)); + if (!priv->iface_modem_location_parent->enable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_quectel_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + gboolean start_gps = FALSE; + + priv = get_private (MM_SHARED_QUECTEL (self)); + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->enable_location_gathering); + g_assert (priv->iface_modem_location_parent->enable_location_gathering_finish); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); + + /* Check if the source is provided by the parent */ + if (!(priv->provided_sources & source)) { + priv->iface_modem_location_parent->enable_location_gathering ( + self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); + return; + } + + /* Only start GPS engine if not done already */ + start_gps = ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) && + !(priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))); + + if (start_gps) { + mm_base_modem_at_sequence ( + MM_BASE_MODEM (self), + gps_startup, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + (GAsyncReadyCallback)gps_startup_ready, + task); + return; + } + + /* If the GPS is already running just return */ + priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Disable location gathering (Location interface) */ + +gboolean +mm_shared_quectel_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +qgps_end_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +disable_location_gathering_parent_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_QUECTEL (self)); + if (!priv->iface_modem_location_parent->disable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_quectel_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + priv = get_private (MM_SHARED_QUECTEL (self)); + g_assert (priv->iface_modem_location_parent); + + task = g_task_new (self, NULL, callback, user_data); + priv->enabled_sources &= ~source; + + /* Pass handling to parent if we don't handle it */ + if (!(source & priv->provided_sources)) { + /* The step to disable location gathering may not exist */ + if (priv->iface_modem_location_parent->disable_location_gathering && + priv->iface_modem_location_parent->disable_location_gathering_finish) { + priv->iface_modem_location_parent->disable_location_gathering (self, + source, + (GAsyncReadyCallback)disable_location_gathering_parent_ready, + task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Turn off gps on the modem if the source uses gps, + * and there are no other gps sources enabled */ + if ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) && + !(priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) { + /* Close the data port if we don't need it anymore */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_RAW | MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) { + MMPortSerialGps *gps_port; + + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+QGPSEND", + 3, + FALSE, + (GAsyncReadyCallback)qgps_end_ready, + task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Check support (Time interface) */ + +gboolean +mm_shared_quectel_time_check_support_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +support_cclk_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + /* error never returned */ + g_task_return_boolean (task, !!mm_base_modem_at_command_finish (self, res, NULL)); + g_object_unref (task); +} + +static void +support_cclk_query (GTask *task) +{ + MMBaseModem *self; + + self = g_task_get_source_object (task); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CCLK?", + 3, + FALSE, + (GAsyncReadyCallback)support_cclk_query_ready, + task); +} + +static void +ctzu_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + mm_obj_warn (self, "couldn't enable automatic time zone update: %s", error->message); + + support_cclk_query (task); +} + +static void +ctzu_test_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + const gchar *response; + gboolean supports_disable; + gboolean supports_enable; + gboolean supports_enable_update_rtc; + const gchar *cmd = NULL; + + /* If CTZU isn't supported, run CCLK right away */ + response = mm_base_modem_at_command_finish (self, res, NULL); + if (!response) { + support_cclk_query (task); + return; + } + + if (!mm_quectel_parse_ctzu_test_response (response, + self, + &supports_disable, + &supports_enable, + &supports_enable_update_rtc, + &error)) { + mm_obj_warn (self, "couldn't parse +CTZU test response: %s", error->message); + support_cclk_query (task); + return; + } + + /* Custom time support check because some Quectel modems (e.g. EC25) require + * +CTZU=3 in order to have the CCLK? time reported in localtime, instead of + * UTC time. */ + if (supports_enable_update_rtc) + cmd = "+CTZU=3"; + else if (supports_enable) + cmd = "+CTZU=1"; + + if (!cmd) { + mm_obj_warn (self, "unknown +CTZU support"); + support_cclk_query (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 3, + FALSE, + (GAsyncReadyCallback)ctzu_set_ready, + task); +} + +void +mm_shared_quectel_time_check_support (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CTZU=?", + 3, + TRUE, /* cached! */ + (GAsyncReadyCallback)ctzu_test_ready, + task); +} + +/*****************************************************************************/ + +static void +shared_quectel_init (gpointer g_iface) +{ +} + +GType +mm_shared_quectel_get_type (void) +{ + static GType shared_quectel_type = 0; + + if (!G_UNLIKELY (shared_quectel_type)) { + static const GTypeInfo info = { + sizeof (MMSharedQuectel), /* class_size */ + shared_quectel_init, /* base_init */ + NULL, /* base_finalize */ + }; + + shared_quectel_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedQuectel", &info, 0); + g_type_interface_add_prerequisite (shared_quectel_type, MM_TYPE_IFACE_MODEM_FIRMWARE); + } + + return shared_quectel_type; +} diff --git a/src/plugins/quectel/mm-shared-quectel.h b/src/plugins/quectel/mm-shared-quectel.h new file mode 100644 index 00000000..0dfcbde4 --- /dev/null +++ b/src/plugins/quectel/mm-shared-quectel.h @@ -0,0 +1,93 @@ +/* -*- 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) 2018-2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_SHARED_QUECTEL_H +#define MM_SHARED_QUECTEL_H + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-firmware.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-time.h" + +#define MM_TYPE_SHARED_QUECTEL (mm_shared_quectel_get_type ()) +#define MM_SHARED_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_QUECTEL, MMSharedQuectel)) +#define MM_IS_SHARED_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_QUECTEL)) +#define MM_SHARED_QUECTEL_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_QUECTEL, MMSharedQuectel)) + +typedef struct _MMSharedQuectel MMSharedQuectel; + +struct _MMSharedQuectel { + GTypeInterface g_iface; + MMBroadbandModemClass * (* peek_parent_broadband_modem_class) (MMSharedQuectel *self); + MMIfaceModem * (* peek_parent_modem_interface) (MMSharedQuectel *self); + MMIfaceModemLocation * (* peek_parent_modem_location_interface) (MMSharedQuectel *self); +}; + +GType mm_shared_quectel_get_type (void); + +void mm_shared_quectel_setup_ports (MMBroadbandModem *self); + +void mm_shared_quectel_firmware_load_update_settings (MMIfaceModemFirmware *self, + GAsyncReadyCallback callback, + gpointer user_data); + +MMFirmwareUpdateSettings *mm_shared_quectel_firmware_load_update_settings_finish (MMIfaceModemFirmware *self, + GAsyncResult *res, + GError **error); + +void mm_shared_quectel_setup_sim_hot_swap (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_quectel_setup_sim_hot_swap_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); +void mm_shared_quectel_cleanup_sim_hot_swap (MMIfaceModem *self); + +void mm_shared_quectel_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMModemLocationSource mm_shared_quectel_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_quectel_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_quectel_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_quectel_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_quectel_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); + +void mm_shared_quectel_time_check_support (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_quectel_time_check_support_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error); + +#endif /* MM_SHARED_QUECTEL_H */ diff --git a/src/plugins/quectel/tests/test-modem-helpers-quectel.c b/src/plugins/quectel/tests/test-modem-helpers-quectel.c new file mode 100644 index 00000000..0e2c7420 --- /dev/null +++ b/src/plugins/quectel/tests/test-modem-helpers-quectel.c @@ -0,0 +1,93 @@ +/* -*- 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) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> +#include <math.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-quectel.h" + +/*****************************************************************************/ +/* Test ^CTZU test responses */ + +typedef struct { + const gchar *response; + gboolean expect_supports_disable; + gboolean expect_supports_enable; + gboolean expect_supports_enable_update_rtc; +} TestCtzuResponse; + +static const TestCtzuResponse test_ctzu_response[] = { + { "+CTZU: (0,1)", TRUE, TRUE, FALSE }, + { "+CTZU: (0,1,3)", TRUE, TRUE, TRUE }, +}; + +static void +common_test_ctzu (const gchar *response, + gboolean expect_supports_disable, + gboolean expect_supports_enable, + gboolean expect_supports_enable_update_rtc) +{ + g_autoptr(GError) error = NULL; + gboolean res; + gboolean supports_disable = FALSE; + gboolean supports_enable = FALSE; + gboolean supports_enable_update_rtc = FALSE; + + res = mm_quectel_parse_ctzu_test_response (response, + NULL, + &supports_disable, + &supports_enable, + &supports_enable_update_rtc, + &error); + g_assert_no_error (error); + g_assert (res); + + g_assert_cmpuint (expect_supports_disable, ==, supports_disable); + g_assert_cmpuint (expect_supports_enable, ==, supports_enable); + g_assert_cmpuint (expect_supports_enable_update_rtc, ==, supports_enable_update_rtc); +} + +static void +test_ctzu (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (test_ctzu_response); i++) + common_test_ctzu (test_ctzu_response[i].response, + test_ctzu_response[i].expect_supports_disable, + test_ctzu_response[i].expect_supports_enable, + test_ctzu_response[i].expect_supports_enable_update_rtc); +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/quectel/ctzu", test_ctzu); + + return g_test_run (); +} diff --git a/src/plugins/samsung/mm-broadband-modem-samsung.c b/src/plugins/samsung/mm-broadband-modem-samsung.c new file mode 100644 index 00000000..1ffc178f --- /dev/null +++ b/src/plugins/samsung/mm-broadband-modem-samsung.c @@ -0,0 +1,94 @@ +/* -*- 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 Samsung Electronics, Inc. + * Copyright (C) 2012 Google Inc. + * Author: Nathan Williams <njw@google.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-samsung.h" +#include "mm-broadband-bearer-icera.h" +#include "mm-modem-helpers.h" + +G_DEFINE_TYPE (MMBroadbandModemSamsung, mm_broadband_modem_samsung, MM_TYPE_BROADBAND_MODEM_ICERA) + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *self) +{ + MMPortSerialAt *ports[2]; + guint i; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_samsung_parent_class)->setup_ports (self); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Configure AT ports */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + g_object_set (ports[i], + MM_PORT_SERIAL_SEND_DELAY, (guint64) 0, + NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemSamsung * +mm_broadband_modem_samsung_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_SAMSUNG, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer (AT) and Icera bearer (NET) supported */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + /* We want to use DHCP always! */ + MM_BROADBAND_MODEM_ICERA_DEFAULT_IP_METHOD, MM_BEARER_IP_METHOD_DHCP, + NULL); +} + +static void +mm_broadband_modem_samsung_init (MMBroadbandModemSamsung *self) +{ +} + +static void +mm_broadband_modem_samsung_class_init (MMBroadbandModemSamsungClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/samsung/mm-broadband-modem-samsung.h b/src/plugins/samsung/mm-broadband-modem-samsung.h new file mode 100644 index 00000000..106f973c --- /dev/null +++ b/src/plugins/samsung/mm-broadband-modem-samsung.h @@ -0,0 +1,49 @@ +/* -*- 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 Samsung Electronics, Inc. + * Copyright (C) 2012 Google Inc. + * Author: Nathan Williams <njw@google.com> + */ + +#ifndef MM_BROADBAND_MODEM_SAMSUNG_H +#define MM_BROADBAND_MODEM_SAMSUNG_H + +#include "mm-broadband-modem-icera.h" + +#define MM_TYPE_BROADBAND_MODEM_SAMSUNG (mm_broadband_modem_samsung_get_type ()) +#define MM_BROADBAND_MODEM_SAMSUNG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_SAMSUNG, MMBroadbandModemSamsung)) +#define MM_BROADBAND_MODEM_SAMSUNG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_SAMSUNG, MMBroadbandModemSamsungClass)) +#define MM_IS_BROADBAND_MODEM_SAMSUNG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_SAMSUNG)) +#define MM_IS_BROADBAND_MODEM_SAMSUNG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_SAMSUNG)) +#define MM_BROADBAND_MODEM_SAMSUNG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_SAMSUNG, MMBroadbandModemSamsungClass)) + +typedef struct _MMBroadbandModemSamsung MMBroadbandModemSamsung; +typedef struct _MMBroadbandModemSamsungClass MMBroadbandModemSamsungClass; + +struct _MMBroadbandModemSamsung { + MMBroadbandModemIcera parent; +}; + +struct _MMBroadbandModemSamsungClass{ + MMBroadbandModemIceraClass parent; +}; + +GType mm_broadband_modem_samsung_get_type (void); + +MMBroadbandModemSamsung *mm_broadband_modem_samsung_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_SAMSUNG_H */ diff --git a/src/plugins/samsung/mm-plugin-samsung.c b/src/plugins/samsung/mm-plugin-samsung.c new file mode 100644 index 00000000..3ce64d73 --- /dev/null +++ b/src/plugins/samsung/mm-plugin-samsung.c @@ -0,0 +1,83 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2011 Samsung Electronics, Inc. + * Copyright (C) 2012 Google Inc. + * Author: Nathan Williams <njw@google.com> + */ + +#include <string.h> +#include <gmodule.h> + +#include "mm-plugin-samsung.h" +#include "mm-private-boxed-types.h" +#include "mm-broadband-modem-samsung.h" + +G_DEFINE_TYPE (MMPluginSamsung, mm_plugin_samsung, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_samsung_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", NULL }; + static const mm_uint16_pair products[] = { { 0x04e8, 0x6872 }, + { 0x04e8, 0x6906 }, + { 0, 0 } }; + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_SAMSUNG, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_PRODUCT_IDS, products, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_SEND_DELAY, (guint64) 0, + NULL)); +} + +static void +mm_plugin_samsung_init (MMPluginSamsung *self) +{ +} + +static void +mm_plugin_samsung_class_init (MMPluginSamsungClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/samsung/mm-plugin-samsung.h b/src/plugins/samsung/mm-plugin-samsung.h new file mode 100644 index 00000000..85f32028 --- /dev/null +++ b/src/plugins/samsung/mm-plugin-samsung.h @@ -0,0 +1,48 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2011 Samsung Electronics, Inc. + * Copyright (C) 2012 Google Inc. + * Author: Nathan Williams <njw@google.com> + */ + +#ifndef MM_PLUGIN_SAMSUNG_H +#define MM_PLUGIN_SAMSUNG_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_SAMSUNG (mm_plugin_samsung_get_type ()) +#define MM_PLUGIN_SAMSUNG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_SAMSUNG, MMPluginSamsung)) +#define MM_PLUGIN_SAMSUNG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_SAMSUNG, MMPluginSamsungClass)) +#define MM_IS_PLUGIN_SAMSUNG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_SAMSUNG)) +#define MM_IS_PLUGIN_SAMSUNG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_SAMSUNG)) +#define MM_PLUGIN_SAMSUNG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_SAMSUNG, MMPluginSamsungClass)) + +typedef struct { + MMPlugin parent; +} MMPluginSamsung; + +typedef struct { + MMPluginClass parent; +} MMPluginSamsungClass; + +GType mm_plugin_samsung_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_SAMSUNG_H */ diff --git a/src/plugins/sierra/77-mm-sierra.rules b/src/plugins/sierra/77-mm-sierra.rules new file mode 100644 index 00000000..e26e9a40 --- /dev/null +++ b/src/plugins/sierra/77-mm-sierra.rules @@ -0,0 +1,40 @@ + +# do not edit this file, it will be overwritten on update +ACTION!="add|change|move|bind", GOTO="mm_sierra_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1199", GOTO="mm_sierra_generic" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1519", GOTO="mm_sierra_comneon" +GOTO="mm_sierra_end" + +LABEL="mm_sierra_generic" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Netgear AC341U: enable connection status polling explicitly +ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9057", ENV{ID_MM_QMI_CONNECTION_STATUS_POLLING_ENABLE}="1" + +# EM7345: disable CPOL based features +ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1" + +# MC74XX: Add port hints +# if 03: primary port +# if 02: raw NMEA port +# if 00: diag/qcdm port +ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9071", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9071", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9071", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" + +# EM7565: Add port hints +# if 03: primary port +# if 02: raw NMEA port +# if 00: diag/qcdm port +ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9091", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9091", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9091", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" + +GOTO="mm_sierra_end" + +LABEL="mm_sierra_comneon" + +# GL7600: disable CPOL based features +ATTRS{idVendor}=="1519", ATTRS{idProduct}=="0443", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1" + +LABEL="mm_sierra_end" diff --git a/src/plugins/sierra/mm-broadband-bearer-sierra.c b/src/plugins/sierra/mm-broadband-bearer-sierra.c new file mode 100644 index 00000000..5c540389 --- /dev/null +++ b/src/plugins/sierra/mm-broadband-bearer-sierra.c @@ -0,0 +1,682 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-sierra.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-sierra.h" + +G_DEFINE_TYPE (MMBroadbandBearerSierra, mm_broadband_bearer_sierra, MM_TYPE_BROADBAND_BEARER); + +struct _MMBroadbandBearerSierraPrivate { + gboolean is_icera; +}; + +enum { + PROP_0, + PROP_IS_ICERA, + PROP_LAST +}; + +/*****************************************************************************/ +/* Connection status monitoring */ + +static MMBearerConnectionStatus +load_connection_status_finish (MMBaseBearer *bearer, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_BEARER_CONNECTION_STATUS_UNKNOWN; + } + return (MMBearerConnectionStatus)value; +} + +static void +scact_periodic_query_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + GList *pdp_active_list = NULL; + GList *l; + MMBearerConnectionStatus status = MM_BEARER_CONNECTION_STATUS_UNKNOWN; + guint cid; + + cid = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (response) + pdp_active_list = mm_sierra_parse_scact_read_response (response, &error); + + if (error) { + g_assert (!pdp_active_list); + g_prefix_error (&error, "Couldn't check current list of active PDP contexts: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + for (l = pdp_active_list; l; l = g_list_next (l)) { + MM3gppPdpContextActive *pdp_active; + + /* We look for the specific CID */ + pdp_active = (MM3gppPdpContextActive *)(l->data); + if (pdp_active->cid == cid) { + status = (pdp_active->active ? MM_BEARER_CONNECTION_STATUS_CONNECTED : MM_BEARER_CONNECTION_STATUS_DISCONNECTED); + break; + } + } + mm_3gpp_pdp_context_active_list_free (pdp_active_list); + + /* PDP context not found? This shouldn't happen, error out */ + if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "PDP context not found in the known contexts list"); + else + g_task_return_int (task, (gssize) status); + g_object_unref (task); +} + +static void +load_connection_status (MMBaseBearer *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMBaseModem *modem = NULL; + MMPortSerialAt *port; + gint profile_id; + + task = g_task_new (self, NULL, callback, user_data); + + g_object_get (MM_BASE_BEARER (self), + MM_BASE_BEARER_MODEM, &modem, + NULL); + + /* If CID not defined, error out */ + profile_id = mm_base_bearer_get_profile_id (self); + if (profile_id == MM_3GPP_PROFILE_ID_UNKNOWN) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't load connection status: profile id not defined"); + g_object_unref (task); + goto out; + } + g_task_set_task_data (task, GUINT_TO_POINTER ((guint)profile_id), NULL); + + /* If no control port available, error out */ + port = mm_base_modem_peek_best_at_port (modem, NULL); + if (!port) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Couldn't load connection status: no control port available"); + g_object_unref (task); + goto out; + } + + mm_base_modem_at_command_full (MM_BASE_MODEM (modem), + port, + "!SCACT?", + 3, + FALSE, /* allow cached */ + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback) scact_periodic_query_ready, + task); + +out: + g_clear_object (&modem); +} + +/*****************************************************************************/ +/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ + +typedef enum { + DIAL_3GPP_STEP_FIRST, + DIAL_3GPP_STEP_PS_ATTACH, + DIAL_3GPP_STEP_AUTHENTICATE, + DIAL_3GPP_STEP_CONNECT, + DIAL_3GPP_STEP_LAST +} Dial3gppStep; + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + guint cid; + MMPort *data; + Dial3gppStep step; +} Dial3gppContext; + +static void +dial_3gpp_context_free (Dial3gppContext *ctx) +{ + if (ctx->data) + g_object_unref (ctx->data); + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_slice_free (Dial3gppContext, ctx); +} + +static MMPort * +dial_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void dial_3gpp_context_step (GTask *task); + +static void +parent_dial_3gpp_ready (MMBroadbandBearer *self, + GAsyncResult *res, + GTask *task) +{ + Dial3gppContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + ctx->data = MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_sierra_parent_class)->dial_3gpp_finish (self, res, &error); + if (!ctx->data) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go on */ + ctx->step++; + dial_3gpp_context_step (task); +} + +static void +scact_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + Dial3gppContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go on */ + ctx->step++; + dial_3gpp_context_step (task); +} + +static void +authenticate_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + Dial3gppContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go on */ + ctx->step++; + dial_3gpp_context_step (task); +} + +static void +cgatt_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + Dial3gppContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go on */ + ctx->step++; + dial_3gpp_context_step (task); +} + +static void +dial_3gpp_context_step (GTask *task) +{ + MMBroadbandBearerSierra *self; + Dial3gppContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + switch (ctx->step) { + case DIAL_3GPP_STEP_FIRST: + ctx->step++; + /* fall through */ + + case DIAL_3GPP_STEP_PS_ATTACH: + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "+CGATT=1", + 10, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)cgatt_ready, + task); + return; + + case DIAL_3GPP_STEP_AUTHENTICATE: + if (!MM_IS_PORT_SERIAL_AT (ctx->data)) { + gchar *command; + const gchar *user; + const gchar *password; + MMBearerAllowedAuth allowed_auth; + + user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + allowed_auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + + if (!user || !password || allowed_auth == MM_BEARER_ALLOWED_AUTH_NONE) { + mm_obj_dbg (self, "not using authentication"); + if (self->priv->is_icera) + command = g_strdup_printf ("%%IPDPCFG=%d,0,0,\"\",\"\"", ctx->cid); + else + command = g_strdup_printf ("$QCPDPP=%d,0", ctx->cid); + } else { + gchar *quoted_user; + gchar *quoted_password; + guint sierra_auth; + + if (allowed_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN) { + mm_obj_dbg (self, "using default (CHAP) authentication method"); + sierra_auth = 2; + } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_CHAP) { + mm_obj_dbg (self, "using CHAP authentication method"); + sierra_auth = 2; + } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_PAP) { + mm_obj_dbg (self, "using PAP authentication method"); + sierra_auth = 1; + } else { + gchar *str; + + str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth); + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot use any of the specified authentication methods (%s)", + str); + g_free (str); + g_object_unref (task); + return; + } + + quoted_user = mm_port_serial_at_quote_string (user); + quoted_password = mm_port_serial_at_quote_string (password); + if (self->priv->is_icera) { + command = g_strdup_printf ("%%IPDPCFG=%d,0,%u,%s,%s", + ctx->cid, + sierra_auth, + quoted_user, + quoted_password); + } else { + /* Yes, password comes first... */ + command = g_strdup_printf ("$QCPDPP=%d,%u,%s,%s", + ctx->cid, + sierra_auth, + quoted_password, + quoted_user); + } + g_free (quoted_user); + g_free (quoted_password); + } + + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)authenticate_ready, + task); + g_free (command); + return; + } + + ctx->step++; + /* fall through */ + + case DIAL_3GPP_STEP_CONNECT: + /* We need a net or AT data port */ + ctx->data = mm_base_modem_get_best_data_port (ctx->modem, MM_PORT_TYPE_NET); + if (ctx->data) { + gchar *command; + + command = g_strdup_printf ("!SCACT=1,%d", ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)scact_ready, + task); + g_free (command); + return; + } + + /* Chain up parent's dialling if we don't have a net port */ + MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_sierra_parent_class)->dial_3gpp ( + MM_BROADBAND_BEARER (self), + ctx->modem, + ctx->primary, + ctx->cid, + g_task_get_cancellable (task), + (GAsyncReadyCallback)parent_dial_3gpp_ready, + task); + return; + + case DIAL_3GPP_STEP_LAST: + g_task_return_pointer (task, + g_object_ref (ctx->data), + g_object_unref); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +dial_3gpp (MMBroadbandBearer *self, + MMBaseModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Dial3gppContext *ctx; + GTask *task; + + g_assert (primary != NULL); + + ctx = g_slice_new0 (Dial3gppContext); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + ctx->step = DIAL_3GPP_STEP_FIRST; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)dial_3gpp_context_free); + + dial_3gpp_context_step (task); +} + +/*****************************************************************************/ +/* 3GPP disconnect */ + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_disconnect_3gpp_ready (MMBroadbandBearer *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_sierra_parent_class)->disconnect_3gpp_finish (self, res, &error)) { + mm_obj_dbg (self, "parent disconnection failed (not fatal): %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +disconnect_scact_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerSierra *self; + GError *error = NULL; + + self = g_task_get_source_object (task); + + /* Ignore errors for now */ + mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + mm_obj_dbg (self, "disconnection failed (not fatal): %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +disconnect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + g_assert (primary != NULL); + + task = g_task_new (self, NULL, callback, user_data); + + if (!MM_IS_PORT_SERIAL_AT (data)) { + gchar *command; + + /* Use specific CID */ + command = g_strdup_printf ("!SCACT=0,%u", cid); + mm_base_modem_at_command_full (MM_BASE_MODEM (modem), + primary, + command, + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)disconnect_scact_ready, + task); + g_free (command); + return; + } + + /* Chain up parent's disconnection if we don't have a net port */ + MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_sierra_parent_class)->disconnect_3gpp ( + self, + modem, + primary, + secondary, + data, + cid, + (GAsyncReadyCallback)parent_disconnect_3gpp_ready, + task); +} + +/*****************************************************************************/ + +#define MM_BROADBAND_BEARER_SIERRA_IS_ICERA "is-icera" + +MMBaseBearer * +mm_broadband_bearer_sierra_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *bearer; + GObject *source; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_sierra_new (MMBroadbandModem *modem, + MMBearerProperties *config, + gboolean is_icera, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_SIERRA, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + MM_BROADBAND_BEARER_SIERRA_IS_ICERA, is_icera, + NULL); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MMBroadbandBearerSierra *self = MM_BROADBAND_BEARER_SIERRA (object); + + switch (prop_id) { + case PROP_IS_ICERA: + self->priv->is_icera = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MMBroadbandBearerSierra *self = MM_BROADBAND_BEARER_SIERRA (object); + + switch (prop_id) { + case PROP_IS_ICERA: + g_value_set_boolean (value, self->priv->is_icera); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +mm_broadband_bearer_sierra_init (MMBroadbandBearerSierra *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_BEARER_SIERRA, + MMBroadbandBearerSierraPrivate); +} + +static void +mm_broadband_bearer_sierra_class_init (MMBroadbandBearerSierraClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandBearerSierraPrivate)); + + object_class->set_property = set_property; + object_class->get_property = get_property; + + base_bearer_class->load_connection_status = load_connection_status; + base_bearer_class->load_connection_status_finish = load_connection_status_finish; +#if defined WITH_SUSPEND_RESUME + base_bearer_class->reload_connection_status = load_connection_status; + base_bearer_class->reload_connection_status_finish = load_connection_status_finish; +#endif + + broadband_bearer_class->dial_3gpp = dial_3gpp; + broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; + + g_object_class_install_property (object_class, PROP_IS_ICERA, + g_param_spec_boolean (MM_BROADBAND_BEARER_SIERRA_IS_ICERA, + "IsIcera", + "Whether the modem uses Icera commands or not.", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} diff --git a/src/plugins/sierra/mm-broadband-bearer-sierra.h b/src/plugins/sierra/mm-broadband-bearer-sierra.h new file mode 100644 index 00000000..9f75685e --- /dev/null +++ b/src/plugins/sierra/mm-broadband-bearer-sierra.h @@ -0,0 +1,62 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#ifndef MM_BROADBAND_BEARER_SIERRA_H +#define MM_BROADBAND_BEARER_SIERRA_H + +#include <glib.h> +#include <glib-object.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-sierra.h" + +#define MM_TYPE_BROADBAND_BEARER_SIERRA (mm_broadband_bearer_sierra_get_type ()) +#define MM_BROADBAND_BEARER_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_SIERRA, MMBroadbandBearerSierra)) +#define MM_BROADBAND_BEARER_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_SIERRA, MMBroadbandBearerSierraClass)) +#define MM_IS_BROADBAND_BEARER_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_SIERRA)) +#define MM_IS_BROADBAND_BEARER_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_SIERRA)) +#define MM_BROADBAND_BEARER_SIERRA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_SIERRA, MMBroadbandBearerSierraClass)) + +typedef struct _MMBroadbandBearerSierra MMBroadbandBearerSierra; +typedef struct _MMBroadbandBearerSierraClass MMBroadbandBearerSierraClass; +typedef struct _MMBroadbandBearerSierraPrivate MMBroadbandBearerSierraPrivate; + +struct _MMBroadbandBearerSierra { + MMBroadbandBearer parent; + MMBroadbandBearerSierraPrivate *priv; +}; + +struct _MMBroadbandBearerSierraClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_sierra_get_type (void); + +/* Default 3GPP bearer creation implementation */ +void mm_broadband_bearer_sierra_new (MMBroadbandModem *modem, + MMBearerProperties *config, + gboolean is_icera, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_sierra_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_BROADBAND_BEARER_SIERRA_H */ diff --git a/src/plugins/sierra/mm-broadband-modem-sierra-icera.c b/src/plugins/sierra/mm-broadband-modem-sierra-icera.c new file mode 100644 index 00000000..c7dfcf81 --- /dev/null +++ b/src/plugins/sierra/mm-broadband-modem-sierra-icera.c @@ -0,0 +1,145 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-broadband-modem-sierra-icera.h" +#include "mm-iface-modem.h" +#include "mm-modem-helpers.h" +#include "mm-log-object.h" +#include "mm-common-sierra.h" +#include "mm-broadband-bearer-sierra.h" + +static void iface_modem_init (MMIfaceModem *iface); + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemSierraIcera, mm_broadband_modem_sierra_icera, MM_TYPE_BROADBAND_MODEM_ICERA, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)) + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_sierra_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_sierra_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + + g_object_unref (task); +} + +static void +modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_obj_dbg (self, "creating sierra bearer..."); + mm_broadband_bearer_sierra_new (MM_BROADBAND_MODEM (self), + properties, + TRUE, /* is_icera */ + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_sierra_new_ready, + task); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *self) +{ + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_sierra_icera_parent_class)->setup_ports (self); + + mm_common_sierra_setup_ports (self); +} + +/*****************************************************************************/ + +MMBroadbandModemSierraIcera * +mm_broadband_modem_sierra_icera_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Sierra bearer supports both NET and TTY */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_sierra_icera_init (MMBroadbandModemSierraIcera *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + mm_common_sierra_peek_parent_interfaces (iface); + + iface->load_power_state = mm_common_sierra_load_power_state; + iface->load_power_state_finish = mm_common_sierra_load_power_state_finish; + iface->modem_power_up = mm_common_sierra_modem_power_up; + iface->modem_power_up_finish = mm_common_sierra_modem_power_up_finish; + iface->create_sim = mm_common_sierra_create_sim; + iface->create_sim_finish = mm_common_sierra_create_sim_finish; + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; +} + +static void +mm_broadband_modem_sierra_icera_class_init (MMBroadbandModemSierraIceraClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/sierra/mm-broadband-modem-sierra-icera.h b/src/plugins/sierra/mm-broadband-modem-sierra-icera.h new file mode 100644 index 00000000..97b07d19 --- /dev/null +++ b/src/plugins/sierra/mm-broadband-modem-sierra-icera.h @@ -0,0 +1,49 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#ifndef MM_BROADBAND_MODEM_SIERRA_ICERA_H +#define MM_BROADBAND_MODEM_SIERRA_ICERA_H + +#include "mm-broadband-modem-icera.h" + +#define MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA (mm_broadband_modem_sierra_icera_get_type ()) +#define MM_BROADBAND_MODEM_SIERRA_ICERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA, MMBroadbandModemSierraIcera)) +#define MM_BROADBAND_MODEM_SIERRA_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA, MMBroadbandModemSierraIceraClass)) +#define MM_IS_BROADBAND_MODEM_SIERRA_ICERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA)) +#define MM_IS_BROADBAND_MODEM_SIERRA_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA)) +#define MM_BROADBAND_MODEM_SIERRA_ICERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA, MMBroadbandModemSierraIceraClass)) + +typedef struct _MMBroadbandModemSierraIcera MMBroadbandModemSierraIcera; +typedef struct _MMBroadbandModemSierraIceraClass MMBroadbandModemSierraIceraClass; + +struct _MMBroadbandModemSierraIcera { + MMBroadbandModemIcera parent; +}; + +struct _MMBroadbandModemSierraIceraClass { + MMBroadbandModemIceraClass parent; +}; + +GType mm_broadband_modem_sierra_icera_get_type (void); + +MMBroadbandModemSierraIcera *mm_broadband_modem_sierra_icera_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_SIERRA_ICERA_H */ diff --git a/src/plugins/sierra/mm-broadband-modem-sierra.c b/src/plugins/sierra/mm-broadband-modem-sierra.c new file mode 100644 index 00000000..83611f6d --- /dev/null +++ b/src/plugins/sierra/mm-broadband-modem-sierra.c @@ -0,0 +1,1937 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-broadband-modem-sierra.h" +#include "mm-base-modem-at.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-common-helpers.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-cdma.h" +#include "mm-iface-modem-time.h" +#include "mm-common-sierra.h" +#include "mm-broadband-bearer-sierra.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_cdma_init (MMIfaceModemCdma *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModemCdma *iface_modem_cdma_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemSierra, mm_broadband_modem_sierra, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)); + +typedef enum { + TIME_METHOD_UNKNOWN = 0, + TIME_METHOD_TIME = 1, + TIME_METHOD_SYSTIME = 2, +} TimeMethod; + +struct _MMBroadbandModemSierraPrivate { + TimeMethod time_method; +}; + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + MMUnlockRetries *unlock_retries; + const gchar *response; + gint matched; + guint a, b, c ,d; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return NULL; + + matched = sscanf (response, "+CPINC: %d,%d,%d,%d", + &a, &b, &c, &d); + if (matched != 4) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Could not parse PIN retries results: '%s'", + response); + return NULL; + } + + if (a > 998) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid PIN attempts left: '%u'", + a); + return NULL; + } + + unlock_retries = mm_unlock_retries_new (); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN, a); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN2, b); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK, c); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK2, d); + return unlock_retries; +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CPINC?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Generic AT!STATUS parsing */ + +typedef enum { + SYS_MODE_UNKNOWN, + SYS_MODE_NO_SERVICE, + SYS_MODE_CDMA_1X, + SYS_MODE_EVDO_REV0, + SYS_MODE_EVDO_REVA +} SysMode; + +#define MODEM_REG_TAG "Modem has registered" +#define GENERIC_ROAM_TAG "Roaming:" +#define ROAM_1X_TAG "1xRoam:" +#define ROAM_EVDO_TAG "HDRRoam:" +#define SYS_MODE_TAG "Sys Mode:" +#define SYS_MODE_NO_SERVICE_TAG "NO SRV" +#define SYS_MODE_EVDO_TAG "HDR" +#define SYS_MODE_1X_TAG "1x" +#define SYS_MODE_CDMA_TAG "CDMA" +#define EVDO_REV_TAG "HDR Revision:" +#define SID_TAG "SID:" + +static gboolean +get_roam_value (const gchar *reply, + const gchar *tag, + gboolean is_eri, + gboolean *out_roaming) +{ + gchar *p; + gboolean success; + guint32 ind = 0; + + p = strstr (reply, tag); + if (!p) + return FALSE; + + p += strlen (tag); + while (*p && isspace (*p)) + p++; + + /* Use generic ERI parsing if it's an ERI */ + if (is_eri) { + success = mm_cdma_parse_eri (p, out_roaming, &ind, NULL); + if (success) { + /* Sierra redefines ERI 0, 1, and 2 */ + if (ind == 0) + *out_roaming = FALSE; /* home */ + else if (ind == 1 || ind == 2) + *out_roaming = TRUE; /* roaming */ + } + return success; + } + + /* If it's not an ERI, roaming is just true/false */ + if (*p == '1') { + *out_roaming = TRUE; + return TRUE; + } else if (*p == '0') { + *out_roaming = FALSE; + return TRUE; + } + + return FALSE; +} + +static gboolean +sys_mode_has_service (SysMode mode) +{ + return ( mode == SYS_MODE_CDMA_1X + || mode == SYS_MODE_EVDO_REV0 + || mode == SYS_MODE_EVDO_REVA); +} + +static gboolean +sys_mode_is_evdo (SysMode mode) +{ + return (mode == SYS_MODE_EVDO_REV0 || mode == SYS_MODE_EVDO_REVA); +} + +static gboolean +parse_status (const char *response, + MMModemCdmaRegistrationState *out_cdma1x_state, + MMModemCdmaRegistrationState *out_evdo_state, + MMModemAccessTechnology *out_act) +{ + gchar **lines; + gchar **iter; + gboolean registered = FALSE; + gboolean have_sid = FALSE; + SysMode evdo_mode = SYS_MODE_UNKNOWN; + SysMode sys_mode = SYS_MODE_UNKNOWN; + gboolean evdo_roam = FALSE, cdma1x_roam = FALSE; + + lines = g_strsplit_set (response, "\n\r", 0); + if (!lines) + return FALSE; + + /* Sierra CDMA parts have two general formats depending on whether they + * support EVDO or not. EVDO parts report both 1x and EVDO roaming status + * while of course 1x parts only report 1x status. Some modems also do not + * report the Roaming information (MP 555 GPS). + * + * AT!STATUS responses: + * + * Unregistered MC5725: + * ----------------------- + * Current band: PCS CDMA + * Current channel: 350 + * SID: 0 NID: 0 1xRoam: 0 HDRRoam: 0 + * Temp: 33 State: 100 Sys Mode: NO SRV + * Pilot NOT acquired + * Modem has NOT registered + * + * Registered MC5725: + * ----------------------- + * Current band: Cellular Sleep + * Current channel: 775 + * SID: 30 NID: 2 1xRoam: 0 HDRRoam: 0 + * Temp: 29 State: 200 Sys Mode: HDR + * Pilot acquired + * Modem has registered + * HDR Revision: A + * + * Unregistered AC580: + * ----------------------- + * Current band: PCS CDMA + * Current channel: 350 + * SID: 0 NID: 0 Roaming: 0 + * Temp: 39 State: 100 Scan Mode: 0 + * Pilot NOT acquired + * Modem has NOT registered + * + * Registered AC580: + * ----------------------- + * Current band: Cellular Sleep + * Current channel: 548 + * SID: 26 NID: 1 Roaming: 1 + * Temp: 39 State: 200 Scan Mode: 0 + * Pilot Acquired + * Modem has registered + */ + + /* We have to handle the two formats slightly differently; for newer formats + * with "Sys Mode", we consider the modem registered if the Sys Mode is not + * "NO SRV". The explicit registration status is just icing on the cake. + * For older formats (no "Sys Mode") we treat the modem as registered if + * the SID is non-zero. + */ + + for (iter = lines; iter && *iter; iter++) { + gboolean bool_val = FALSE; + char *p; + + if (!strncmp (*iter, MODEM_REG_TAG, strlen (MODEM_REG_TAG))) { + registered = TRUE; + continue; + } + + /* Roaming */ + get_roam_value (*iter, ROAM_1X_TAG, TRUE, &cdma1x_roam); + get_roam_value (*iter, ROAM_EVDO_TAG, TRUE, &evdo_roam); + if (get_roam_value (*iter, GENERIC_ROAM_TAG, FALSE, &bool_val)) + cdma1x_roam = evdo_roam = bool_val; + + /* Current system mode */ + p = strstr (*iter, SYS_MODE_TAG); + if (p) { + p += strlen (SYS_MODE_TAG); + while (*p && isspace (*p)) + p++; + if (!strncmp (p, SYS_MODE_NO_SERVICE_TAG, strlen (SYS_MODE_NO_SERVICE_TAG))) + sys_mode = SYS_MODE_NO_SERVICE; + else if (!strncmp (p, SYS_MODE_EVDO_TAG, strlen (SYS_MODE_EVDO_TAG))) + sys_mode = SYS_MODE_EVDO_REV0; + else if ( !strncmp (p, SYS_MODE_1X_TAG, strlen (SYS_MODE_1X_TAG)) + || !strncmp (p, SYS_MODE_CDMA_TAG, strlen (SYS_MODE_CDMA_TAG))) + sys_mode = SYS_MODE_CDMA_1X; + } + + /* Current EVDO revision if system mode is EVDO */ + p = strstr (*iter, EVDO_REV_TAG); + if (p) { + p += strlen (EVDO_REV_TAG); + while (*p && isspace (*p)) + p++; + if (*p == 'A') + evdo_mode = SYS_MODE_EVDO_REVA; + else if (*p == '0') + evdo_mode = SYS_MODE_EVDO_REV0; + } + + /* SID */ + p = strstr (*iter, SID_TAG); + if (p) { + p += strlen (SID_TAG); + while (*p && isspace (*p)) + p++; + if (isdigit (*p) && (*p != '0')) + have_sid = TRUE; + } + } + + /* Update current system mode */ + if (sys_mode_is_evdo (sys_mode)) { + /* Prefer the explicit EVDO mode from EVDO_REV_TAG */ + if (evdo_mode != SYS_MODE_UNKNOWN) + sys_mode = evdo_mode; + } + + /* If the modem didn't report explicit registration with "Modem has + * registered" then get registration status by looking at either system + * mode or (for older devices that don't report that) just the SID. + */ + if (!registered) { + if (sys_mode != SYS_MODE_UNKNOWN) + registered = sys_mode_has_service (sys_mode); + else + registered = have_sid; + } + + if (registered) { + *out_cdma1x_state = (cdma1x_roam ? + MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING : + MM_MODEM_CDMA_REGISTRATION_STATE_HOME); + + if (sys_mode_is_evdo (sys_mode)) { + *out_evdo_state = (evdo_roam ? + MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING : + MM_MODEM_CDMA_REGISTRATION_STATE_HOME); + } else { + *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + } + } else { + /* Not registered */ + *out_cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + } + + if (out_act) { + *out_act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + if (registered) { + if (sys_mode == SYS_MODE_CDMA_1X) + *out_act = MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; + else if (sys_mode == SYS_MODE_EVDO_REV0) + *out_act = MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; + else if (sys_mode == SYS_MODE_EVDO_REVA) + *out_act = MM_MODEM_ACCESS_TECHNOLOGY_EVDOA; + } + } + + g_strfreev (lines); + + return TRUE; +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +typedef struct { + MMModemAccessTechnology act; + guint mask; +} AccessTechInfo; + +static AccessTechInfo * +access_tech_info_new (MMModemAccessTechnology act, + guint mask) +{ + AccessTechInfo *info; + + info = g_new (AccessTechInfo, 1); + info->act = act; + info->mask = mask; + + return info; +} + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + AccessTechInfo *info; + + info = g_task_propagate_pointer (G_TASK (res), error); + if (!info) + return FALSE; + + *access_technologies = info->act; + *mask = info->mask; + g_free (info); + return TRUE; +} + +static void +access_tech_3gpp_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) + g_task_return_error (task, error); + else { + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + const gchar *p; + + p = mm_strip_tag (response, "*CNTI:"); + p = strchr (p, ','); + if (p) + act = mm_string_to_access_tech (p + 1); + + if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN) + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse access technologies result: '%s'", + response); + else + g_task_return_pointer ( + task, + access_tech_info_new (act, MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK), + g_free); + } + + g_object_unref (task); +} + +static void +access_tech_cdma_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) + g_task_return_error (task, error); + else { + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + MMModemCdmaRegistrationState cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + + if (!parse_status (response, &cdma1x_state, &evdo_state, &act)) + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse access technologies result: '%s'", + response); + else + g_task_return_pointer ( + task, + access_tech_info_new (act, MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK), + g_free); + } + + g_object_unref (task); +} + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + if (mm_iface_modem_is_3gpp (self)) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*CNTI=0", + 3, + FALSE, + (GAsyncReadyCallback)access_tech_3gpp_ready, + task); + return; + } + + if (mm_iface_modem_is_cdma (self)) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "!STATUS", + 3, + FALSE, + (GAsyncReadyCallback)access_tech_cdma_ready, + task); + return; + } + + g_assert_not_reached (); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMModemModeCombination mode; + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* CDMA-only modems don't support changing modes, default to parent's */ + if (!mm_iface_modem_is_3gpp (self)) { + g_task_return_pointer (task, all, (GDestroyNotify) g_array_unref); + g_object_unref (task); + return; + } + + /* Build list of combinations for 3GPP devices */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + /* Non-LTE devices allow 2G/3G preferred modes */ + if (!mm_iface_modem_is_3gpp_lte (self)) { + /* 2G and 3G, 2G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + /* 2G and 3G, 3G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + } else { + /* 4G only */ + mode.allowed = MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G, 3G and 4G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +typedef struct { + MMModemMode allowed; + MMModemMode preferred; +} LoadCurrentModesResult; + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + g_autofree LoadCurrentModesResult *result = NULL; + + result = g_task_propagate_pointer (G_TASK (res), error); + if (!result) + return FALSE; + + *allowed = result->allowed; + *preferred = result->preferred; + return TRUE; +} + +static void +selrat_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autofree LoadCurrentModesResult *result = NULL; + const gchar *response; + GError *error = NULL; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + response = mm_base_modem_at_command_full_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + result = g_new0 (LoadCurrentModesResult, 1); + + /* Example response: !SELRAT: 03, UMTS 3G Preferred */ + r = g_regex_new ("!SELRAT:\\s*(\\d+).*$", 0, 0, NULL); + g_assert (r != NULL); + + if (g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &error)) { + guint mode; + + if (mm_get_uint_from_match_info (match_info, 1, &mode) && mode <= 7) { + switch (mode) { + case 0: + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + result->preferred = MM_MODEM_MODE_NONE; + if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) + result->allowed |= MM_MODEM_MODE_4G; + result->preferred = MM_MODEM_MODE_NONE; + break; + case 1: + result->allowed = MM_MODEM_MODE_3G; + result->preferred = MM_MODEM_MODE_NONE; + break; + case 2: + result->allowed = MM_MODEM_MODE_2G; + result->preferred = MM_MODEM_MODE_NONE; + break; + case 3: + /* in Sierra LTE devices, mode 3 is automatic, including LTE, no preference */ + if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) { + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + result->preferred = MM_MODEM_MODE_NONE; + } else { + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + result->preferred = MM_MODEM_MODE_3G; + } + break; + case 4: + /* in Sierra LTE devices, mode 4 is automatic, including LTE, no preference */ + if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) { + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + result->preferred = MM_MODEM_MODE_NONE; + } else { + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + result->preferred = MM_MODEM_MODE_2G; + } + break; + case 5: + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + result->preferred = MM_MODEM_MODE_NONE; + break; + case 6: + result->allowed = MM_MODEM_MODE_4G; + result->preferred = MM_MODEM_MODE_NONE; + break; + case 7: + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + result->preferred = MM_MODEM_MODE_NONE; + break; + default: + g_assert_not_reached (); + break; + } + } else + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse the allowed mode response: '%s'", + response); + } else if (!error) + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Could not parse allowed mode response: Response didn't match: '%s'", + response); + + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, g_steal_pointer (&result), g_free); + g_object_unref (task); +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMPortSerialAt *primary; + + task = g_task_new (self, NULL, callback, user_data); + + if (!mm_iface_modem_is_3gpp (self)) { + /* Cannot do this in CDMA modems */ + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot load allowed modes in CDMA modems"); + g_object_unref (task); + return; + } + + /* Sierra secondary ports don't have full AT command interpreters */ + primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + if (!primary || mm_port_get_connected (MM_PORT (primary))) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_CONNECTED, + "Cannot load allowed modes while connected"); + g_object_unref (task); + return; + } + + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + primary, + "!SELRAT?", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)selrat_query_ready, + task); +} + +/*****************************************************************************/ +/* Set current modes (Modem interface) */ + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +selrat_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_full_finish (self, res, &error)) + /* Let the error be critical. */ + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMPortSerialAt *primary; + gint idx = -1; + gchar *command; + + task = g_task_new (self, NULL, callback, user_data); + + if (!mm_iface_modem_is_3gpp (self)) { + /* Cannot do this in CDMA modems */ + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot set allowed modes in CDMA modems"); + g_object_unref (task); + return; + } + + /* Sierra secondary ports don't have full AT command interpreters */ + primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + if (!primary || mm_port_get_connected (MM_PORT (primary))) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_CONNECTED, + "Cannot set allowed modes while connected"); + g_object_unref (task); + return; + } + + if (allowed == MM_MODEM_MODE_3G) + idx = 1; + else if (allowed == MM_MODEM_MODE_2G) + idx = 2; + else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { + /* in Sierra LTE devices, modes 3 and 4 are automatic, including LTE, no preference */ + if (mm_iface_modem_is_3gpp_lte (self)) { + if (preferred == MM_MODEM_MODE_NONE) + idx = 5; /* GSM and UMTS Only */ + } + else if (preferred == MM_MODEM_MODE_3G) + idx = 3; + else if (preferred == MM_MODEM_MODE_2G) + idx = 4; + else if (preferred == MM_MODEM_MODE_NONE) + idx = 0; + } else if (allowed == MM_MODEM_MODE_4G) + idx = 6; + else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G) && + preferred == MM_MODEM_MODE_NONE) + idx = 7; + else if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE) + idx = 0; + + if (idx < 0) { + 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_object_unref (task); + + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("!SELRAT=%d", idx); + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + primary, + command, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)selrat_set_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* After SIM unlock (Modem interface) */ + +static gboolean +modem_after_sim_unlock_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +after_sim_unlock_wait_cb (GTask *task) +{ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +modem_after_sim_unlock (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + guint timeout = 8; + const gchar **drivers; + guint i; + + /* A short wait is necessary for SIM to become ready, otherwise some older + * cards (AC881) crash if asked to connect immediately after sending the + * PIN. Assume sierra_net driven devices are better and don't need as long + * a delay. + */ + drivers = mm_base_modem_get_drivers (MM_BASE_MODEM (self)); + for (i = 0; drivers[i]; i++) { + if (g_str_equal (drivers[i], "sierra_net")) + timeout = 3; + } + + task = g_task_new (self, NULL, callback, user_data); + + g_timeout_add_seconds (timeout, (GSourceFunc)after_sim_unlock_wait_cb, task); +} + +/*****************************************************************************/ +/* Load own numbers (Modem interface) */ + +static GStrv +modem_load_own_numbers_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_own_numbers_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GStrv numbers; + + numbers = iface_modem_parent->load_own_numbers_finish (self, res, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, numbers, (GDestroyNotify)g_strfreev); + + g_object_unref (task); +} + +#define MDN_TAG "MDN: " + +static void +own_numbers_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response, *p; + const gchar *numbers[2] = { NULL, NULL }; + gchar mdn[15]; + guint i; + + response = mm_base_modem_at_command_finish (self, res, NULL); + if (!response) + goto fallback; + + p = strstr (response, MDN_TAG); + if (!p) + goto fallback; + + response = p + strlen (MDN_TAG); + while (isspace (*response)) + response++; + + for (i = 0; i < (sizeof (mdn) - 1) && isdigit (response[i]); i++) + mdn[i] = response[i]; + mdn[i] = '\0'; + numbers[0] = &mdn[0]; + + /* MDNs are 10 digits in length */ + if (i != 10) { + mm_obj_warn (self, "failed to parse MDN: expected 10 digits, got %d", i); + goto fallback; + } + + g_task_return_pointer (task, + g_strdupv ((gchar **) numbers), + (GDestroyNotify)g_strfreev); + g_object_unref (task); + return; + +fallback: + /* Fall back to parent method */ + iface_modem_parent->load_own_numbers ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_own_numbers_ready, + task); +} + +static void +modem_load_own_numbers (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* 3GPP modems can just run parent's own number loading */ + if (mm_iface_modem_is_3gpp (self)) { + iface_modem_parent->load_own_numbers ( + self, + (GAsyncReadyCallback)parent_load_own_numbers_ready, + task); + return; + } + + /* CDMA modems try AT~NAMVAL?0 first, then fall back to parent for + * loading own number from NV memory with QCDM. + */ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "~NAMVAL?0", + 3, + FALSE, + (GAsyncReadyCallback)own_numbers_ready, + task); +} + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_sierra_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_sierra_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + + g_object_unref (task); +} + +static void +modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_obj_dbg (self, "creating Sierra bearer..."); + mm_broadband_bearer_sierra_new (MM_BROADBAND_MODEM (self), + properties, + FALSE, /* is_icera */ + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_sierra_new_ready, + task); +} + +/*****************************************************************************/ +/* Reset (Modem interface) */ + +static gboolean +modem_reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "!RESET", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +modem_power_down_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + /* Ignore errors for now; we're not sure if all Sierra CDMA devices support + * at!pcstate or 3GPP devices support +CFUN=4. + */ + mm_base_modem_at_command_finish (self, res, NULL); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* For CDMA modems, run !pcstate */ + if (mm_iface_modem_is_cdma_only (self)) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "!pcstate=0", + 5, + FALSE, + (GAsyncReadyCallback)modem_power_down_ready, + task); + return; + } + + /* For GSM modems, run AT+CFUN=4 (power save) */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=4", + 3, + FALSE, + (GAsyncReadyCallback)modem_power_down_ready, + task); +} + +/*****************************************************************************/ +/* Setup registration checks (CDMA interface) */ + +typedef struct { + gboolean skip_qcdm_call_manager_step; + gboolean skip_qcdm_hdr_step; + gboolean skip_at_cdma_service_status_step; + gboolean skip_at_cdma1x_serving_system_step; + gboolean skip_detailed_registration_state; +} SetupRegistrationChecksResults; + +static gboolean +setup_registration_checks_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + gboolean *skip_qcdm_call_manager_step, + gboolean *skip_qcdm_hdr_step, + gboolean *skip_at_cdma_service_status_step, + gboolean *skip_at_cdma1x_serving_system_step, + gboolean *skip_detailed_registration_state, + GError **error) +{ + SetupRegistrationChecksResults *results; + + results = g_task_propagate_pointer (G_TASK (res), error); + if (!results) + return FALSE; + + *skip_qcdm_call_manager_step = results->skip_qcdm_call_manager_step; + *skip_qcdm_hdr_step = results->skip_qcdm_hdr_step; + *skip_at_cdma_service_status_step = results->skip_at_cdma_service_status_step; + *skip_at_cdma1x_serving_system_step = results->skip_at_cdma1x_serving_system_step; + *skip_detailed_registration_state = results->skip_detailed_registration_state; + g_free (results); + return TRUE; +} + +static void +parent_setup_registration_checks_ready (MMIfaceModemCdma *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + SetupRegistrationChecksResults *results; + + results = g_new0 (SetupRegistrationChecksResults, 1); + if (!iface_modem_cdma_parent->setup_registration_checks_finish (self, + res, + &results->skip_qcdm_call_manager_step, + &results->skip_qcdm_hdr_step, + &results->skip_at_cdma_service_status_step, + &results->skip_at_cdma1x_serving_system_step, + &results->skip_detailed_registration_state, + &error)) { + g_task_return_error (task, error); + g_free (results); + } else { + /* Skip +CSS */ + results->skip_at_cdma1x_serving_system_step = TRUE; + /* Skip +CAD */ + results->skip_at_cdma_service_status_step = TRUE; + + /* Force to always use the detailed registration checks, as we have + * !STATUS for that */ + results->skip_detailed_registration_state = FALSE; + + g_task_return_pointer (task, results, g_free); + } + g_object_unref (task); +} + +static void +setup_registration_checks (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Run parent's checks first */ + iface_modem_cdma_parent->setup_registration_checks (self, + (GAsyncReadyCallback)parent_setup_registration_checks_ready, + task); +} + +/*****************************************************************************/ +/* Detailed registration state (CDMA interface) */ + +typedef struct { + MMModemCdmaRegistrationState detailed_cdma1x_state; + MMModemCdmaRegistrationState detailed_evdo_state; +} DetailedRegistrationStateResults; + +static gboolean +get_detailed_registration_state_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + MMModemCdmaRegistrationState *detailed_cdma1x_state, + MMModemCdmaRegistrationState *detailed_evdo_state, + GError **error) +{ + DetailedRegistrationStateResults *results; + + results = g_task_propagate_pointer (G_TASK (res), error); + if (!results) + return FALSE; + + *detailed_cdma1x_state = results->detailed_cdma1x_state; + *detailed_evdo_state = results->detailed_evdo_state; + g_free (results); + return TRUE; +} + +static void +status_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + DetailedRegistrationStateResults *results; + const gchar *response; + + results = g_task_get_task_data (task); + + /* If error, leave superclass' reg state alone if AT!STATUS isn't supported. */ + response = mm_base_modem_at_command_finish (self, res, NULL); + if (response) + parse_status (response, + &(results->detailed_cdma1x_state), + &(results->detailed_evdo_state), + NULL); + + g_task_return_pointer (task, g_memdup (results, sizeof (*results)), g_free); + g_object_unref (task); +} + +static void +get_detailed_registration_state (MMIfaceModemCdma *self, + MMModemCdmaRegistrationState cdma1x_state, + MMModemCdmaRegistrationState evdo_state, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DetailedRegistrationStateResults *results; + GTask *task; + + results = g_new0 (DetailedRegistrationStateResults, 1); + results->detailed_cdma1x_state = cdma1x_state; + results->detailed_evdo_state = evdo_state; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, results, g_free); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "!STATUS", + 3, + FALSE, + (GAsyncReadyCallback)status_ready, + task); +} + +/*****************************************************************************/ +/* Automatic activation (CDMA interface) */ + +typedef enum { + CDMA_AUTOMATIC_ACTIVATION_STEP_FIRST, + CDMA_AUTOMATIC_ACTIVATION_STEP_UNLOCK, + CDMA_AUTOMATIC_ACTIVATION_STEP_CDV, + CDMA_AUTOMATIC_ACTIVATION_STEP_CHECK, + CDMA_AUTOMATIC_ACTIVATION_STEP_LAST +} CdmaAutomaticActivationStep; + +typedef struct { + CdmaAutomaticActivationStep step; + gchar *carrier_code; +} CdmaAutomaticActivationContext; + +static void +cdma_automatic_activation_context_free (CdmaAutomaticActivationContext *ctx) +{ + g_free (ctx->carrier_code); + g_slice_free (CdmaAutomaticActivationContext, ctx); +} + +static gboolean +modem_cdma_activate_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void cdma_automatic_activation_step (GTask *task); + +static void +automatic_activation_command_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + CdmaAutomaticActivationContext *ctx; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Keep on */ + ctx = g_task_get_task_data (task); + ctx->step++; + cdma_automatic_activation_step (task); +} + +static void +cdma_automatic_activation_step (GTask *task) +{ + MMBroadbandModemSierra *self; + CdmaAutomaticActivationContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case CDMA_AUTOMATIC_ACTIVATION_STEP_FIRST: + ctx->step++; + /* fall-through */ + + case CDMA_AUTOMATIC_ACTIVATION_STEP_UNLOCK: + mm_obj_msg (self, "activation step [1/4]: unlocking device"); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "~NAMLCK=000000", + 20, + FALSE, + (GAsyncReadyCallback)automatic_activation_command_ready, + task); + return; + + case CDMA_AUTOMATIC_ACTIVATION_STEP_CDV: { + gchar *command; + + mm_obj_msg (self, "activation step [2/4]: requesting OTASP"); + command = g_strdup_printf ("+CDV%s", ctx->carrier_code); + mm_base_modem_at_command (MM_BASE_MODEM (self), + command, + 120, + FALSE, + (GAsyncReadyCallback)automatic_activation_command_ready, + task); + g_free (command); + return; + } + + case CDMA_AUTOMATIC_ACTIVATION_STEP_CHECK: + mm_obj_msg (self, "activation step [3/4]: checking activation info"); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "~NAMVAL?0", + 3, + FALSE, + (GAsyncReadyCallback)automatic_activation_command_ready, + task); + return; + + case CDMA_AUTOMATIC_ACTIVATION_STEP_LAST: + mm_obj_msg (self, "activation step [4/4]: activation process finished"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +modem_cdma_activate (MMIfaceModemCdma *self, + const gchar *carrier_code, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + CdmaAutomaticActivationContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + + /* Setup context */ + ctx = g_slice_new0 (CdmaAutomaticActivationContext); + ctx->carrier_code = g_strdup (carrier_code); + ctx->step = CDMA_AUTOMATIC_ACTIVATION_STEP_FIRST; + g_task_set_task_data (task, ctx, (GDestroyNotify)cdma_automatic_activation_context_free); + + /* And start it */ + cdma_automatic_activation_step (task); +} + +/*****************************************************************************/ +/* Manual activation (CDMA interface) */ + +typedef enum { + CDMA_MANUAL_ACTIVATION_STEP_FIRST, + CDMA_MANUAL_ACTIVATION_STEP_SPC, + CDMA_MANUAL_ACTIVATION_STEP_MDN_MIN, + CDMA_MANUAL_ACTIVATION_STEP_OTASP, + CDMA_MANUAL_ACTIVATION_STEP_CHECK, + CDMA_MANUAL_ACTIVATION_STEP_LAST +} CdmaManualActivationStep; + +typedef struct { + CdmaManualActivationStep step; + MMCdmaManualActivationProperties *properties; +} CdmaManualActivationContext; + +static gboolean +modem_cdma_activate_manual_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void cdma_manual_activation_step (GTask *task); + +static void +manual_activation_command_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + CdmaManualActivationContext *ctx; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Keep on */ + ctx = g_task_get_task_data (task); + ctx->step++; + cdma_manual_activation_step (task); +} + +static void +cdma_manual_activation_step (GTask *task) +{ + MMBroadbandModemSierra *self; + CdmaManualActivationContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case CDMA_MANUAL_ACTIVATION_STEP_FIRST: + ctx->step++; + /* fall-through */ + + case CDMA_MANUAL_ACTIVATION_STEP_SPC: { + gchar *command; + + mm_obj_msg (self, "activation step [1/5]: unlocking device"); + command = g_strdup_printf ("~NAMLCK=%s", + mm_cdma_manual_activation_properties_get_spc (ctx->properties)); + mm_base_modem_at_command (MM_BASE_MODEM (self), + command, + 20, + FALSE, + (GAsyncReadyCallback)manual_activation_command_ready, + task); + g_free (command); + return; + } + + case CDMA_MANUAL_ACTIVATION_STEP_MDN_MIN: { + gchar *command; + + mm_obj_msg (self, "activation step [2/5]: setting MDN/MIN/SID"); + command = g_strdup_printf ("~NAMVAL=0,%s,%s,%" G_GUINT16_FORMAT ",65535", + mm_cdma_manual_activation_properties_get_mdn (ctx->properties), + mm_cdma_manual_activation_properties_get_min (ctx->properties), + mm_cdma_manual_activation_properties_get_sid (ctx->properties)); + mm_base_modem_at_command (MM_BASE_MODEM (self), + command, + 120, + FALSE, + (GAsyncReadyCallback)manual_activation_command_ready, + task); + g_free (command); + return; + } + + case CDMA_MANUAL_ACTIVATION_STEP_OTASP: + mm_obj_msg (self, "activation step [3/5]: requesting OTASP"); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "!IOTASTART", + 20, + FALSE, + (GAsyncReadyCallback)manual_activation_command_ready, + task); + return; + + case CDMA_MANUAL_ACTIVATION_STEP_CHECK: + mm_obj_msg (self, "activation step [4/5]: checking activation info"); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "~NAMVAL?0", + 20, + FALSE, + (GAsyncReadyCallback)manual_activation_command_ready, + task); + return; + + case CDMA_MANUAL_ACTIVATION_STEP_LAST: + mm_obj_msg (self, "activation step [5/5]: activation process finished"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +modem_cdma_activate_manual (MMIfaceModemCdma *self, + MMCdmaManualActivationProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + CdmaManualActivationContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + + /* Setup context */ + ctx = g_slice_new0 (CdmaManualActivationContext); + ctx->properties = g_object_ref (properties); + ctx->step = CDMA_MANUAL_ACTIVATION_STEP_FIRST; + g_task_set_task_data (task, ctx, (GDestroyNotify)cdma_automatic_activation_context_free); + + /* And start it */ + cdma_manual_activation_step (task); +} + +/*****************************************************************************/ +/* Load network time (Time interface) */ + +static gchar * +parse_time (const gchar *response, + const gchar *regex, + const gchar *tag, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *match_error = NULL; + guint year; + guint month; + guint day; + guint hour; + guint minute; + guint second; + gchar *result = NULL; + + r = g_regex_new (regex, 0, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse %s results: ", tag); + } else { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match %s reply", tag); + } + } else { + if (mm_get_uint_from_match_info (match_info, 1, &year) && + mm_get_uint_from_match_info (match_info, 2, &month) && + mm_get_uint_from_match_info (match_info, 3, &day) && + mm_get_uint_from_match_info (match_info, 4, &hour) && + mm_get_uint_from_match_info (match_info, 5, &minute) && + mm_get_uint_from_match_info (match_info, 6, &second)) { + result = mm_new_iso8601_time (year, month, day, hour, minute, second, FALSE, 0, error); + } else { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse %s reply", tag); + } + } + + return result; +} + + +static gchar * +parse_3gpp_time (const gchar *response, GError **error) +{ + /* Returns both local time and UTC time, but we have no good way to + * determine the timezone from all of that, so just report local time. + */ + return parse_time (response, + "\\s*!TIME:\\s+" + "(\\d+)/(\\d+)/(\\d+)\\s+" + "(\\d+):(\\d+):(\\d+)\\s*\\(local\\)\\s+" + "(\\d+)/(\\d+)/(\\d+)\\s+" + "(\\d+):(\\d+):(\\d+)\\s*\\(UTC\\)\\s*", + "!TIME", + error); +} + +static gchar * +parse_cdma_time (const gchar *response, GError **error) +{ + /* YYYYMMDDWHHMMSS */ + return parse_time (response, + "\\s*(\\d{4})(\\d{2})(\\d{2})\\d(\\d{2})(\\d{2})(\\d{2})\\s*", + "!SYSTIME", + error); +} + +static gchar * +modem_time_load_network_time_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + const gchar *response = NULL; + char *iso8601 = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (response) { + if (strstr (response, "!TIME:")) + iso8601 = parse_3gpp_time (response, error); + else + iso8601 = parse_cdma_time (response, error); + } + return iso8601; +} + +static void +modem_time_load_network_time (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + const char *command; + + switch (MM_BROADBAND_MODEM_SIERRA (self)->priv->time_method) { + case TIME_METHOD_TIME: + command = "!TIME?"; + break; + case TIME_METHOD_SYSTIME: + command = "!SYSTIME?"; + break; + case TIME_METHOD_UNKNOWN: + default: + g_assert_not_reached (); + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + command, + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Check support (Time interface) */ + +enum { + TIME_SUPPORTED = 1, + SYSTIME_SUPPORTED = 2, +}; + +static gboolean +modem_time_check_support_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +modem_time_check_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GVariant *result; + gboolean supported = FALSE; + + result = mm_base_modem_at_sequence_finish (self, res, NULL, &error); + if (!error && result) { + MMBroadbandModemSierra *sierra = MM_BROADBAND_MODEM_SIERRA (self); + + sierra->priv->time_method = g_variant_get_uint32 (result); + if (sierra->priv->time_method != TIME_METHOD_UNKNOWN) + supported = TRUE; + } + g_clear_error (&error); + + g_task_return_boolean (task, supported); + g_object_unref (task); +} + +static MMBaseModemAtResponseProcessorResult +parse_time_reply (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + *result_error = NULL; + + /* If error, try next command */ + if (!error) { + if (strstr (command, "!TIME")) + *result = g_variant_new_uint32 (TIME_METHOD_TIME); + else if (strstr (command, "!SYSTIME")) + *result = g_variant_new_uint32 (TIME_METHOD_SYSTIME); + } + + /* Stop sequence if we get a result, but not on errors */ + return (*result ? + MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS : + MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE); +} + +static const MMBaseModemAtCommand time_check_sequence[] = { + { "!TIME?", 3, FALSE, parse_time_reply }, /* 3GPP */ + { "!SYSTIME?", 3, FALSE, parse_time_reply }, /* CDMA */ + { NULL } +}; + +static void +modem_time_check_support (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_sequence ( + MM_BASE_MODEM (self), + time_check_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + (GAsyncReadyCallback)modem_time_check_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *self) +{ + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_sierra_parent_class)->setup_ports (self); + + mm_common_sierra_setup_ports (self); +} + +/*****************************************************************************/ + +MMBroadbandModemSierra * +mm_broadband_modem_sierra_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_SIERRA, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Sierra bearer supports both NET and TTY */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_sierra_init (MMBroadbandModemSierra *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_MODEM_SIERRA, + MMBroadbandModemSierraPrivate); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + mm_common_sierra_peek_parent_interfaces (iface); + + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->load_own_numbers = modem_load_own_numbers; + iface->load_own_numbers_finish = modem_load_own_numbers_finish; + iface->reset = modem_reset; + iface->reset_finish = modem_reset_finish; + iface->load_power_state = mm_common_sierra_load_power_state; + iface->load_power_state_finish = mm_common_sierra_load_power_state_finish; + iface->modem_power_up = mm_common_sierra_modem_power_up; + iface->modem_power_up_finish = mm_common_sierra_modem_power_up_finish; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; + iface->create_sim = mm_common_sierra_create_sim; + iface->create_sim_finish = mm_common_sierra_create_sim_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; + iface->modem_after_sim_unlock = modem_after_sim_unlock; + iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish; + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; +} + +static void +iface_modem_cdma_init (MMIfaceModemCdma *iface) +{ + iface_modem_cdma_parent = g_type_interface_peek_parent (iface); + + iface->setup_registration_checks = setup_registration_checks; + iface->setup_registration_checks_finish = setup_registration_checks_finish; + iface->get_detailed_registration_state = get_detailed_registration_state; + iface->get_detailed_registration_state_finish = get_detailed_registration_state_finish; + iface->activate = modem_cdma_activate; + iface->activate_finish = modem_cdma_activate_finish; + iface->activate_manual = modem_cdma_activate_manual; + iface->activate_manual_finish = modem_cdma_activate_manual_finish; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface->check_support = modem_time_check_support; + iface->check_support_finish = modem_time_check_support_finish; + iface->load_network_time = modem_time_load_network_time; + iface->load_network_time_finish = modem_time_load_network_time_finish; +} + +static void +mm_broadband_modem_sierra_class_init (MMBroadbandModemSierraClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemSierraPrivate)); + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/sierra/mm-broadband-modem-sierra.h b/src/plugins/sierra/mm-broadband-modem-sierra.h new file mode 100644 index 00000000..9804b184 --- /dev/null +++ b/src/plugins/sierra/mm-broadband-modem-sierra.h @@ -0,0 +1,51 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#ifndef MM_BROADBAND_MODEM_SIERRA_H +#define MM_BROADBAND_MODEM_SIERRA_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_SIERRA (mm_broadband_modem_sierra_get_type ()) +#define MM_BROADBAND_MODEM_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA, MMBroadbandModemSierra)) +#define MM_BROADBAND_MODEM_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_SIERRA, MMBroadbandModemSierraClass)) +#define MM_IS_BROADBAND_MODEM_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA)) +#define MM_IS_BROADBAND_MODEM_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_SIERRA)) +#define MM_BROADBAND_MODEM_SIERRA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA, MMBroadbandModemSierraClass)) + +typedef struct _MMBroadbandModemSierra MMBroadbandModemSierra; +typedef struct _MMBroadbandModemSierraClass MMBroadbandModemSierraClass; +typedef struct _MMBroadbandModemSierraPrivate MMBroadbandModemSierraPrivate; + +struct _MMBroadbandModemSierra { + MMBroadbandModem parent; + MMBroadbandModemSierraPrivate *priv; +}; + +struct _MMBroadbandModemSierraClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_sierra_get_type (void); + +MMBroadbandModemSierra *mm_broadband_modem_sierra_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_SIERRA_H */ diff --git a/src/plugins/sierra/mm-common-sierra.c b/src/plugins/sierra/mm-common-sierra.c new file mode 100644 index 00000000..72cbc34f --- /dev/null +++ b/src/plugins/sierra/mm-common-sierra.c @@ -0,0 +1,516 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#include <stdlib.h> +#include <string.h> + +#include "mm-common-sierra.h" +#include "mm-base-modem-at.h" +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-sim-sierra.h" + +static MMIfaceModem *iface_modem_parent; + +/*****************************************************************************/ +/* Custom init and port type hints */ + +#define TAG_SIERRA_APP_PORT "sierra-app-port" +#define TAG_SIERRA_APP1_PPP_OK "sierra-app1-ppp-ok" + +gboolean +mm_common_sierra_grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE; + MMPortType ptype; + + ptype = mm_port_probe_get_port_type (probe); + + /* Is it a GSM secondary port? */ + if (g_object_get_data (G_OBJECT (probe), TAG_SIERRA_APP_PORT)) { + if (g_object_get_data (G_OBJECT (probe), TAG_SIERRA_APP1_PPP_OK)) + pflags = MM_PORT_SERIAL_AT_FLAG_PPP; + else + pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY; + } else if (ptype == MM_PORT_TYPE_AT) + pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY; + + return mm_base_modem_grab_port (modem, + mm_port_probe_peek_port (probe), + ptype, + pflags, + error); +} + +gboolean +mm_common_sierra_port_probe_list_is_icera (GList *probes) +{ + GList *l; + + for (l = probes; l; l = g_list_next (l)) { + /* Only assume the Icera probing check is valid IF the port is not + * secondary. This will skip the stupid ports which reply OK to every + * AT command, even the one we use to check for Icera support */ + if (mm_port_probe_is_icera (MM_PORT_PROBE (l->data)) && + !g_object_get_data (G_OBJECT (l->data), TAG_SIERRA_APP_PORT)) + return TRUE; + } + + return FALSE; +} + +typedef struct { + MMPortSerialAt *port; + guint retries; +} SierraCustomInitContext; + +static void +sierra_custom_init_context_free (SierraCustomInitContext *ctx) +{ + g_object_unref (ctx->port); + g_slice_free (SierraCustomInitContext, ctx); +} + +gboolean +mm_common_sierra_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void sierra_custom_init_step (GTask *task); + +static void +gcap_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + MMPortProbe *probe; + SierraCustomInitContext *ctx; + const gchar *response; + GError *error = NULL; + + probe = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + response = mm_port_serial_at_command_finish (port, res, &error); + if (error) { + /* If consumed all tries and the last error was a timeout, assume the + * port is not AT */ + if (ctx->retries == 0 && + g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { + mm_port_probe_set_result_at (probe, FALSE); + } + /* If reported a hard parse error, this port is definitely not an AT + * port, skip trying. */ + else if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_PARSE_FAILED)) { + mm_port_probe_set_result_at (probe, FALSE); + ctx->retries = 0; + } + /* Some Icera-based devices (eg, USB305) have an AT-style port that + * replies to everything with ERROR, so tag as unsupported; sometimes + * the real AT ports do this too, so let a retry tag the port as + * supported if it responds correctly later. */ + else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) { + mm_port_probe_set_result_at (probe, FALSE); + } + + /* Just retry... */ + sierra_custom_init_step (task); + goto out; + } + + /* A valid reply to ATI tells us this is an AT port already */ + mm_port_probe_set_result_at (probe, TRUE); + + /* Sierra APPx ports have limited AT command parsers that just reply with + * "OK" to most commands. These can sometimes be used for PPP while the + * main port is used for status and control, but older modems tend to crash + * or fail PPP. So we allowlist modems that are known to allow PPP on the + * secondary APP ports. + */ + if (strstr (response, "APP1")) { + g_object_set_data (G_OBJECT (probe), TAG_SIERRA_APP_PORT, GUINT_TO_POINTER (TRUE)); + + /* PPP-on-APP1-port allowlist */ + if (strstr (response, "C885") || + strstr (response, "USB 306") || + strstr (response, "MC8790")) + g_object_set_data (G_OBJECT (probe), TAG_SIERRA_APP1_PPP_OK, GUINT_TO_POINTER (TRUE)); + + /* For debugging: let users figure out if their device supports PPP + * on the APP1 port or not. + */ + if (getenv ("MM_SIERRA_APP1_PPP_OK")) { + mm_obj_dbg (probe, "APP1 PPP OK '%s'", response); + g_object_set_data (G_OBJECT (probe), TAG_SIERRA_APP1_PPP_OK, GUINT_TO_POINTER (TRUE)); + } + } else if (strstr (response, "APP2") || + strstr (response, "APP3") || + strstr (response, "APP4")) { + /* Additional APP ports don't support most AT commands, so they cannot + * be used as the primary port. + */ + g_object_set_data (G_OBJECT (probe), TAG_SIERRA_APP_PORT, GUINT_TO_POINTER (TRUE)); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + +out: + if (error) + g_error_free (error); +} + +static void +sierra_custom_init_step (GTask *task) +{ + MMPortProbe *probe; + SierraCustomInitContext *ctx; + GCancellable *cancellable; + + probe = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + cancellable = g_task_get_cancellable (task); + + /* If cancelled, end */ + if (g_cancellable_is_cancelled (cancellable)) { + mm_obj_dbg (probe, "no need to keep on running custom init"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (ctx->retries == 0) { + mm_obj_dbg (probe, "couldn't get port type hints"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + ctx->retries--; + mm_port_serial_at_command ( + ctx->port, + "ATI", + 3, + FALSE, /* raw */ + FALSE, /* allow_cached */ + cancellable, + (GAsyncReadyCallback)gcap_ready, + task); +} + +void +mm_common_sierra_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SierraCustomInitContext *ctx; + GTask *task; + + ctx = g_slice_new (SierraCustomInitContext); + ctx->port = g_object_ref (port); + ctx->retries = 3; + + task = g_task_new (probe, cancellable, callback, user_data); + g_task_set_check_cancellable (task, FALSE); + g_task_set_task_data (task, ctx, (GDestroyNotify)sierra_custom_init_context_free); + + sierra_custom_init_step (task); +} + +/*****************************************************************************/ +/* Modem power up (Modem interface) */ + +gboolean +mm_common_sierra_modem_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +sierra_power_up_wait_cb (GTask *task) +{ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +cfun_enable_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + guint i; + const gchar **drivers; + gboolean is_new_sierra = FALSE; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Many Sierra devices return OK immediately in response to CFUN=1 but + * need some time to finish powering up, otherwise subsequent commands + * may return failure or even crash the modem. Give more time for older + * devices like the AC860 and C885, which aren't driven by the 'sierra_net' + * driver. Assume any DirectIP (ie, sierra_net) device is new enough + * to allow a lower timeout. + */ + drivers = mm_base_modem_get_drivers (MM_BASE_MODEM (self)); + for (i = 0; drivers[i]; i++) { + if (g_str_equal (drivers[i], "sierra_net")) { + is_new_sierra = TRUE; + break; + } + } + + /* The modem object will be valid in the callback as 'task' keeps a + * reference to it. */ + g_timeout_add_seconds (is_new_sierra ? 5 : 10, (GSourceFunc)sierra_power_up_wait_cb, task); +} + +static void +pcstate_enable_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + /* Ignore errors for now; we're not sure if all Sierra CDMA devices support + * at!pcstate. + */ + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_common_sierra_modem_power_up (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* For CDMA modems, run !pcstate */ + if (mm_iface_modem_is_cdma_only (self)) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "!pcstate=1", + 5, + FALSE, + (GAsyncReadyCallback)pcstate_enable_ready, + task); + return; + } + + mm_obj_warn (self, "not in full functionality status, power-up command is needed"); + mm_obj_warn (self, "device may be rebooted"); + + /* Try to go to full functionality mode without rebooting the system. + * Works well if we previously switched off the power with CFUN=4 + */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=1,0", /* ",0" requests no reset */ + 10, + FALSE, + (GAsyncReadyCallback)cfun_enable_ready, + task); +} + +/*****************************************************************************/ +/* Power state loading (Modem interface) */ + +MMModemPowerState +mm_common_sierra_load_power_state_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_POWER_STATE_UNKNOWN; + } + return (MMModemPowerState)value; +} + +static void +parent_load_power_state_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + MMModemPowerState state; + + state = iface_modem_parent->load_power_state_finish (self, res, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_int (task, state); + + g_object_unref (task); +} + +static void +pcstate_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *result; + guint state; + GError *error = NULL; + + result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!result) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Parse power state reply */ + result = mm_strip_tag (result, "!PCSTATE:"); + if (!mm_get_uint_from_str (result, &state)) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse !PCSTATE response '%s'", + result); + } else { + switch (state) { + case 0: + g_task_return_int (task, MM_MODEM_POWER_STATE_LOW); + break; + case 1: + g_task_return_int (task, MM_MODEM_POWER_STATE_ON); + break; + default: + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unhandled power state: '%u'", + state); + break; + } + } + + g_object_unref (task); +} + +void +mm_common_sierra_load_power_state (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Check power state with AT!PCSTATE? */ + if (mm_iface_modem_is_cdma_only (self)) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "!pcstate?", + 3, + FALSE, + (GAsyncReadyCallback)pcstate_query_ready, + task); + return; + } + + /* Otherwise run parent's */ + iface_modem_parent->load_power_state (self, + (GAsyncReadyCallback)parent_load_power_state_ready, + task); +} + +/*****************************************************************************/ +/* Create SIM (Modem interface) */ + +MMBaseSim * +mm_common_sierra_create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_sierra_new_finish (res, error); +} + +void +mm_common_sierra_create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* New Sierra SIM */ + mm_sim_sierra_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + +/*****************************************************************************/ +/* Setup ports */ + +void +mm_common_sierra_setup_ports (MMBroadbandModem *self) +{ + MMPortSerialAt *ports[2]; + guint i; + g_autoptr(GRegex) pacsp_regex = NULL; + + pacsp_regex = g_regex_new ("\\r\\n\\+PACSP.*\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + if (i == 1) { + /* Built-in echo removal conflicts with the APP1 port's limited AT + * parser, which doesn't always prefix responses with <CR><LF>. + */ + g_object_set (ports[i], + MM_PORT_SERIAL_AT_REMOVE_ECHO, FALSE, + NULL); + } + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + pacsp_regex, + NULL, NULL, NULL); + } +} + +/*****************************************************************************/ + +void +mm_common_sierra_peek_parent_interfaces (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); +} diff --git a/src/plugins/sierra/mm-common-sierra.h b/src/plugins/sierra/mm-common-sierra.h new file mode 100644 index 00000000..22471c0f --- /dev/null +++ b/src/plugins/sierra/mm-common-sierra.h @@ -0,0 +1,67 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#ifndef MM_COMMON_SIERRA_H +#define MM_COMMON_SIERRA_H + +#include "mm-plugin.h" +#include "mm-broadband-modem.h" +#include "mm-iface-modem.h" +#include "mm-base-sim.h" + +gboolean mm_common_sierra_grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error); + +gboolean mm_common_sierra_port_probe_list_is_icera (GList *probes); + +void mm_common_sierra_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_common_sierra_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error); + +void mm_common_sierra_load_power_state (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMModemPowerState mm_common_sierra_load_power_state_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_common_sierra_modem_power_up (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_common_sierra_modem_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_common_sierra_create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_common_sierra_create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_common_sierra_setup_ports (MMBroadbandModem *self); + +void mm_common_sierra_peek_parent_interfaces (MMIfaceModem *iface); + +#endif /* MM_COMMON_SIERRA_H */ diff --git a/src/plugins/sierra/mm-modem-helpers-sierra.c b/src/plugins/sierra/mm-modem-helpers-sierra.c new file mode 100644 index 00000000..821be199 --- /dev/null +++ b/src/plugins/sierra/mm-modem-helpers-sierra.c @@ -0,0 +1,76 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> +#include <string.h> + +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-sierra.h" + +GList * +mm_sierra_parse_scact_read_response (const gchar *reply, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + GList *list = NULL; + + if (!reply || !reply[0]) + /* Nothing configured, all done */ + return NULL; + + r = g_regex_new ("!SCACT:\\s*(\\d+),(\\d+)", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, &inner_error); + g_assert (r); + + g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &inner_error); + while (!inner_error && g_match_info_matches (match_info)) { + MM3gppPdpContextActive *pdp_active; + guint cid = 0; + guint aux = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &cid)) { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse CID from reply: '%s'", + reply); + break; + } + if (!mm_get_uint_from_match_info (match_info, 2, &aux) || (aux != 0 && aux != 1)) { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse context status from reply: '%s'", + reply); + break; + } + + pdp_active = g_slice_new0 (MM3gppPdpContextActive); + pdp_active->cid = cid; + pdp_active->active = (gboolean) aux; + list = g_list_prepend (list, pdp_active); + + g_match_info_next (match_info, &inner_error); + } + + if (inner_error) { + mm_3gpp_pdp_context_active_list_free (list); + g_propagate_error (error, inner_error); + g_prefix_error (error, "Couldn't properly parse list of active/inactive PDP contexts. "); + return NULL; + } + + return g_list_sort (list, (GCompareFunc) mm_3gpp_pdp_context_active_cmp); +} diff --git a/src/plugins/sierra/mm-modem-helpers-sierra.h b/src/plugins/sierra/mm-modem-helpers-sierra.h new file mode 100644 index 00000000..f5c6cd2c --- /dev/null +++ b/src/plugins/sierra/mm-modem-helpers-sierra.h @@ -0,0 +1,26 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_MODEM_HELPERS_SIERRA_H +#define MM_MODEM_HELPERS_SIERRA_H + +#include <glib.h> +#include <ModemManager.h> + +/* MM3gppPdpContextActive list */ +GList *mm_sierra_parse_scact_read_response (const gchar *reply, + GError **error); + +#endif /* MM_MODEM_HELPERS_SIERRA_H */ diff --git a/src/plugins/sierra/mm-plugin-sierra-legacy.c b/src/plugins/sierra/mm-plugin-sierra-legacy.c new file mode 100644 index 00000000..521b8ad1 --- /dev/null +++ b/src/plugins/sierra/mm-plugin-sierra-legacy.c @@ -0,0 +1,99 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <stdlib.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-sierra-legacy.h" +#include "mm-common-sierra.h" +#include "mm-broadband-modem-sierra.h" +#include "mm-broadband-modem-sierra-icera.h" + +G_DEFINE_TYPE (MMPluginSierraLegacy, mm_plugin_sierra_legacy, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + if (mm_common_sierra_port_probe_list_is_icera (probes)) + return MM_BASE_MODEM (mm_broadband_modem_sierra_icera_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + + return MM_BASE_MODEM (mm_broadband_modem_sierra_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", NULL }; + static const gchar *drivers[] = { "sierra", "sierra_net", NULL }; + static const gchar *forbidden_drivers[] = { "qmi_wwan", "cdc_mbim", NULL }; + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (mm_common_sierra_custom_init), + .finish = G_CALLBACK (mm_common_sierra_custom_init_finish), + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_SIERRA_LEGACY, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_DRIVERS, drivers, + MM_PLUGIN_FORBIDDEN_DRIVERS, forbidden_drivers, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + MM_PLUGIN_ICERA_PROBE, TRUE, + MM_PLUGIN_REMOVE_ECHO, FALSE, + NULL)); +} + +static void +mm_plugin_sierra_legacy_init (MMPluginSierraLegacy *self) +{ +} + +static void +mm_plugin_sierra_legacy_class_init (MMPluginSierraLegacyClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = mm_common_sierra_grab_port; +} diff --git a/src/plugins/sierra/mm-plugin-sierra-legacy.h b/src/plugins/sierra/mm-plugin-sierra-legacy.h new file mode 100644 index 00000000..787118d6 --- /dev/null +++ b/src/plugins/sierra/mm-plugin-sierra-legacy.h @@ -0,0 +1,40 @@ +/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_SIERRA_LEGACY_H +#define MM_PLUGIN_SIERRA_LEGACY_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_SIERRA_LEGACY (mm_plugin_sierra_legacy_get_type ()) +#define MM_PLUGIN_SIERRA_LEGACY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_SIERRA_LEGACY, MMPluginSierraLegacy)) +#define MM_PLUGIN_SIERRA_LEGACY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_SIERRA_LEGACY, MMPluginSierraLegacyClass)) +#define MM_IS_PLUGIN_SIERRA_LEGACY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_SIERRA_LEGACY)) +#define MM_IS_PLUGIN_SIERRA_LEGACY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_SIERRA_LEGACY)) +#define MM_PLUGIN_SIERRA_LEGACY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_SIERRA_LEGACY, MMPluginSierraLegacyClass)) + +typedef struct { + MMPlugin parent; +} MMPluginSierraLegacy; + +typedef struct { + MMPluginClass parent; +} MMPluginSierraLegacyClass; + +GType mm_plugin_sierra_legacy_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_SIERRA_LEGACY_H */ diff --git a/src/plugins/sierra/mm-plugin-sierra.c b/src/plugins/sierra/mm-plugin-sierra.c new file mode 100644 index 00000000..e4b4b676 --- /dev/null +++ b/src/plugins/sierra/mm-plugin-sierra.c @@ -0,0 +1,137 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <stdlib.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-sierra.h" +#include "mm-broadband-modem.h" +#include "mm-broadband-modem-xmm.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi.h" +#endif + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#include "mm-broadband-modem-mbim-xmm.h" +#endif + +G_DEFINE_TYPE (MMPluginSierra, mm_plugin_sierra, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Sierra modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + if (mm_port_probe_list_is_xmm (probes)) { + mm_obj_dbg (self, "MBIM-powered XMM-based Sierra modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_xmm_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } + mm_obj_dbg (self, "MBIM-powered Sierra modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + if (mm_port_probe_list_is_xmm (probes)) { + mm_obj_dbg (self, "XMM-based Sierra modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_xmm_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } + + /* Fallback to default modem in the worst case */ + return MM_BASE_MODEM (mm_broadband_modem_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x1199, 0 }; + static const gchar *drivers[] = { "qmi_wwan", "cdc_mbim", NULL }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_SIERRA, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_DRIVERS, drivers, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_REQUIRED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + MM_PLUGIN_XMM_PROBE, TRUE, + NULL)); +} + +static void +mm_plugin_sierra_init (MMPluginSierra *self) +{ +} + +static void +mm_plugin_sierra_class_init (MMPluginSierraClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/sierra/mm-plugin-sierra.h b/src/plugins/sierra/mm-plugin-sierra.h new file mode 100644 index 00000000..59b6e6b9 --- /dev/null +++ b/src/plugins/sierra/mm-plugin-sierra.h @@ -0,0 +1,42 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#ifndef MM_PLUGIN_SIERRA_H +#define MM_PLUGIN_SIERRA_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_SIERRA (mm_plugin_sierra_get_type ()) +#define MM_PLUGIN_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_SIERRA, MMPluginSierra)) +#define MM_PLUGIN_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_SIERRA, MMPluginSierraClass)) +#define MM_IS_PLUGIN_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_SIERRA)) +#define MM_IS_PLUGIN_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_SIERRA)) +#define MM_PLUGIN_SIERRA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_SIERRA, MMPluginSierraClass)) + +typedef struct { + MMPlugin parent; +} MMPluginSierra; + +typedef struct { + MMPluginClass parent; +} MMPluginSierraClass; + +GType mm_plugin_sierra_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_SIERRA_H */ diff --git a/src/plugins/sierra/mm-shared.c b/src/plugins/sierra/mm-shared.c new file mode 100644 index 00000000..665aceca --- /dev/null +++ b/src/plugins/sierra/mm-shared.c @@ -0,0 +1,20 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-shared.h" + +MM_SHARED_DEFINE_MAJOR_VERSION +MM_SHARED_DEFINE_MINOR_VERSION +MM_SHARED_DEFINE_NAME(Sierra) diff --git a/src/plugins/sierra/mm-sim-sierra.c b/src/plugins/sierra/mm-sim-sierra.c new file mode 100644 index 00000000..2f3caa48 --- /dev/null +++ b/src/plugins/sierra/mm-sim-sierra.c @@ -0,0 +1,158 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> +#include "mm-modem-helpers.h" +#include "mm-base-modem-at.h" + +#include "mm-sim-sierra.h" + +G_DEFINE_TYPE (MMSimSierra, mm_sim_sierra, MM_TYPE_BASE_SIM) + +/*****************************************************************************/ +/* SIM identifier loading */ + +static gchar * +load_sim_identifier_finish (MMBaseSim *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +iccid_read_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + const gchar *p; + char *parsed; + GError *local = NULL; + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + p = mm_strip_tag (response, "!ICCID:"); + if (!p) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse !ICCID response: '%s'", + response); + g_object_unref (task); + return; + } + + parsed = mm_3gpp_parse_iccid (p, &local); + if (parsed) + g_task_return_pointer (task, parsed, g_free); + else + g_task_return_error (task, local); + + g_object_unref (task); +} + +static void +load_sim_identifier (MMBaseSim *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBaseModem *modem = NULL; + GTask *task; + + g_object_get (self, + MM_BASE_SIM_MODEM, &modem, + NULL); + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command ( + modem, + "!ICCID?", + 3, + FALSE, + (GAsyncReadyCallback)iccid_read_ready, + task); + g_object_unref (modem); +} + +/*****************************************************************************/ + +MMBaseSim * +mm_sim_sierra_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *sim; + + source = g_async_result_get_source_object (res); + sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!sim) + return NULL; + + /* Only export valid SIMs */ + mm_base_sim_export (MM_BASE_SIM (sim)); + + return MM_BASE_SIM (sim); +} + +void +mm_sim_sierra_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (MM_TYPE_SIM_SIERRA, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_SIM_MODEM, modem, + "active", TRUE, /* by default always active */ + NULL); +} + +static void +mm_sim_sierra_init (MMSimSierra *self) +{ +} + +static void +mm_sim_sierra_class_init (MMSimSierraClass *klass) +{ + MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass); + + base_sim_class->load_sim_identifier = load_sim_identifier; + base_sim_class->load_sim_identifier_finish = load_sim_identifier_finish; +} diff --git a/src/plugins/sierra/mm-sim-sierra.h b/src/plugins/sierra/mm-sim-sierra.h new file mode 100644 index 00000000..470d4e95 --- /dev/null +++ b/src/plugins/sierra/mm-sim-sierra.h @@ -0,0 +1,53 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#ifndef MM_SIM_SIERRA_H +#define MM_SIM_SIERRA_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-sim.h" + +#define MM_TYPE_SIM_SIERRA (mm_sim_sierra_get_type ()) +#define MM_SIM_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_SIERRA, MMSimSierra)) +#define MM_SIM_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_SIERRA, MMSimSierraClass)) +#define MM_IS_SIM_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_SIERRA)) +#define MM_IS_SIM_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_SIERRA)) +#define MM_SIM_SIERRA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_SIERRA, MMSimSierraClass)) + +typedef struct _MMSimSierra MMSimSierra; +typedef struct _MMSimSierraClass MMSimSierraClass; + +struct _MMSimSierra { + MMBaseSim parent; +}; + +struct _MMSimSierraClass { + MMBaseSimClass parent; +}; + +GType mm_sim_sierra_get_type (void); + +void mm_sim_sierra_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_sim_sierra_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_SIM_SIERRA_H */ diff --git a/src/plugins/sierra/tests/test-modem-helpers-sierra.c b/src/plugins/sierra/tests/test-modem-helpers-sierra.c new file mode 100644 index 00000000..b0c66496 --- /dev/null +++ b/src/plugins/sierra/tests/test-modem-helpers-sierra.c @@ -0,0 +1,130 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-sierra.h" + +/*****************************************************************************/ +/* Test !SCACT? responses */ + +static void +test_scact_read_results (const gchar *desc, + const gchar *reply, + MM3gppPdpContextActive *expected_results, + guint32 expected_results_len) +{ + GList *l; + GError *error = NULL; + GList *results; + + g_debug ("\nTesting %s !SCACT response...\n", desc); + + results = mm_sierra_parse_scact_read_response (reply, &error); + g_assert_no_error (error); + if (expected_results_len) { + g_assert (results); + g_assert_cmpuint (g_list_length (results), ==, expected_results_len); + } + + for (l = results; l; l = g_list_next (l)) { + MM3gppPdpContextActive *pdp = l->data; + gboolean found = FALSE; + guint i; + + for (i = 0; !found && i < expected_results_len; i++) { + MM3gppPdpContextActive *expected; + + expected = &expected_results[i]; + if (pdp->cid == expected->cid) { + found = TRUE; + g_assert_cmpuint (pdp->active, ==, expected->active); + } + } + + g_assert (found == TRUE); + } + + mm_3gpp_pdp_context_active_list_free (results); +} + +static void +test_scact_read_response_none (void) +{ + test_scact_read_results ("none", "", NULL, 0); +} + +static void +test_scact_read_response_single_inactive (void) +{ + const gchar *reply = "!SCACT: 1,0\r\n"; + static MM3gppPdpContextActive expected[] = { + { 1, FALSE }, + }; + + test_scact_read_results ("single inactive", reply, &expected[0], G_N_ELEMENTS (expected)); +} + +static void +test_scact_read_response_single_active (void) +{ + const gchar *reply = "!SCACT: 1,1\r\n"; + static MM3gppPdpContextActive expected[] = { + { 1, TRUE }, + }; + + test_scact_read_results ("single active", reply, &expected[0], G_N_ELEMENTS (expected)); +} + +static void +test_scact_read_response_multiple (void) +{ + const gchar *reply = + "!SCACT: 1,0\r\n" + "!SCACT: 4,1\r\n" + "!SCACT: 5,0\r\n"; + static MM3gppPdpContextActive expected[] = { + { 1, FALSE }, + { 4, TRUE }, + { 5, FALSE }, + }; + + test_scact_read_results ("multiple", reply, &expected[0], G_N_ELEMENTS (expected)); +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/sierra/scact/read/none", test_scact_read_response_none); + g_test_add_func ("/MM/sierra/scact/read/single-inactive", test_scact_read_response_single_inactive); + g_test_add_func ("/MM/sierra/scact/read/single-active", test_scact_read_response_single_active); + g_test_add_func ("/MM/sierra/scact/read/multiple", test_scact_read_response_multiple); + + return g_test_run (); +} diff --git a/src/plugins/simtech/77-mm-simtech-port-types.rules b/src/plugins/simtech/77-mm-simtech-port-types.rules new file mode 100644 index 00000000..e51a60dd --- /dev/null +++ b/src/plugins/simtech/77-mm-simtech-port-types.rules @@ -0,0 +1,59 @@ +# do not edit this file, it will be overwritten on update + +# Simtech makes modules that other companies rebrand, like: +# +# A-LINK 3GU +# SCT UM300 +# +# Most of these values were scraped from various SimTech-based Windows +# driver .inf files. *mdm.inf lists the main command ports, while +# *ser.inf lists the aux ports that may be used for PPP. + + +ACTION!="add|change|move|bind", GOTO="mm_simtech_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1e0e", GOTO="mm_simtech_port_types" +GOTO="mm_simtech_port_types_end" + +LABEL="mm_simtech_port_types" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# A-LINK 3GU +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Prolink PH-300 +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9100", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9100", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# SCT UM300 +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9200", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9200", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# SIM7000, SIM7100, SIM7600... +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AUDIO}="1" + +# SIM7070, SIM7080, SIM7090 (default layout)... +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1" +# Disable CPOL based features +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1" + + +# SIM7070, SIM7080, SIM7090 (secondary layout)... +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1" +# Disable CPOL based features +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1" + +LABEL="mm_simtech_port_types_end" diff --git a/src/plugins/simtech/mm-broadband-modem-qmi-simtech.c b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.c new file mode 100644 index 00000000..93ff8576 --- /dev/null +++ b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.c @@ -0,0 +1,127 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-errors-types.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" +#include "mm-broadband-modem-qmi-simtech.h" +#include "mm-shared-simtech.h" + +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_voice_init (MMIfaceModemVoice *iface); +static void shared_simtech_init (MMSharedSimtech *iface); + +static MMIfaceModemLocation *iface_modem_location_parent; +static MMIfaceModemVoice *iface_modem_voice_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiSimtech, mm_broadband_modem_qmi_simtech, MM_TYPE_BROADBAND_MODEM_QMI, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_SIMTECH, shared_simtech_init)) + +/*****************************************************************************/ + +MMBroadbandModemQmiSimtech * +mm_broadband_modem_qmi_simtech_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* QMI modem supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_BROADBAND_MODEM_INDICATORS_DISABLED, TRUE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_qmi_simtech_init (MMBroadbandModemQmiSimtech *self) +{ +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_simtech_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_simtech_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_simtech_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_simtech_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_simtech_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_simtech_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedSimtech *self) +{ + return iface_modem_location_parent; +} + +static void +iface_modem_voice_init (MMIfaceModemVoice *iface) +{ + iface_modem_voice_parent = g_type_interface_peek_parent (iface); + + iface->check_support = mm_shared_simtech_voice_check_support; + iface->check_support_finish = mm_shared_simtech_voice_check_support_finish; + iface->enable_unsolicited_events = mm_shared_simtech_voice_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = mm_shared_simtech_voice_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = mm_shared_simtech_voice_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = mm_shared_simtech_voice_disable_unsolicited_events_finish; + iface->setup_unsolicited_events = mm_shared_simtech_voice_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_simtech_voice_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_simtech_voice_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_simtech_voice_cleanup_unsolicited_events_finish; + iface->setup_in_call_audio_channel = mm_shared_simtech_voice_setup_in_call_audio_channel; + iface->setup_in_call_audio_channel_finish = mm_shared_simtech_voice_setup_in_call_audio_channel_finish; + iface->cleanup_in_call_audio_channel = mm_shared_simtech_voice_cleanup_in_call_audio_channel; + iface->cleanup_in_call_audio_channel_finish = mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish; +} + +static MMIfaceModemVoice * +peek_parent_voice_interface (MMSharedSimtech *self) +{ + return iface_modem_voice_parent; +} + +static void +shared_simtech_init (MMSharedSimtech *iface) +{ + iface->peek_parent_location_interface = peek_parent_location_interface; + iface->peek_parent_voice_interface = peek_parent_voice_interface; +} + +static void +mm_broadband_modem_qmi_simtech_class_init (MMBroadbandModemQmiSimtechClass *klass) +{ +} diff --git a/src/plugins/simtech/mm-broadband-modem-qmi-simtech.h b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.h new file mode 100644 index 00000000..2f5b819b --- /dev/null +++ b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.h @@ -0,0 +1,47 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_QMI_SIMTECH_QMI_H +#define MM_BROADBAND_MODEM_QMI_SIMTECH_QMI_H + +#include "mm-broadband-modem-qmi.h" + +#define MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH (mm_broadband_modem_qmi_simtech_get_type ()) +#define MM_BROADBAND_MODEM_QMI_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtech)) +#define MM_BROADBAND_MODEM_QMI_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtechClass)) +#define MM_IS_BROADBAND_MODEM_QMI_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH)) +#define MM_IS_BROADBAND_MODEM_QMI_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH)) +#define MM_BROADBAND_MODEM_QMI_SIMTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtechClass)) + +typedef struct _MMBroadbandModemQmiSimtech MMBroadbandModemQmiSimtech; +typedef struct _MMBroadbandModemQmiSimtechClass MMBroadbandModemQmiSimtechClass; + +struct _MMBroadbandModemQmiSimtech { + MMBroadbandModemQmi parent; +}; + +struct _MMBroadbandModemQmiSimtechClass{ + MMBroadbandModemQmiClass parent; +}; + +GType mm_broadband_modem_qmi_simtech_get_type (void); + +MMBroadbandModemQmiSimtech *mm_broadband_modem_qmi_simtech_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_QMI_SIMTECH_H */ diff --git a/src/plugins/simtech/mm-broadband-modem-simtech.c b/src/plugins/simtech/mm-broadband-modem-simtech.c new file mode 100644 index 00000000..2ca0c6ae --- /dev/null +++ b/src/plugins/simtech/mm-broadband-modem-simtech.c @@ -0,0 +1,1351 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "ModemManager.h" +#include "mm-modem-helpers.h" +#include "mm-log-object.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" +#include "mm-shared-simtech.h" +#include "mm-broadband-modem-simtech.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_voice_init (MMIfaceModemVoice *iface); +static void shared_simtech_init (MMSharedSimtech *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModemLocation *iface_modem_location_parent; +static MMIfaceModemVoice *iface_modem_voice_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemSimtech, mm_broadband_modem_simtech, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_SIMTECH, shared_simtech_init)) + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED +} FeatureSupport; + +struct _MMBroadbandModemSimtechPrivate { + FeatureSupport cnsmod_support; + FeatureSupport autocsq_support; + GRegex *cnsmod_regex; + GRegex *csq_regex; +}; + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static MMModemAccessTechnology +simtech_act_to_mm_act (guint nsmod) +{ + static const MMModemAccessTechnology simtech_act_to_mm_act_map[] = { + [0] = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN, + [1] = MM_MODEM_ACCESS_TECHNOLOGY_GSM, + [2] = MM_MODEM_ACCESS_TECHNOLOGY_GPRS, + [3] = MM_MODEM_ACCESS_TECHNOLOGY_EDGE, + [4] = MM_MODEM_ACCESS_TECHNOLOGY_UMTS, + [5] = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA, + [6] = MM_MODEM_ACCESS_TECHNOLOGY_HSUPA, + [7] = MM_MODEM_ACCESS_TECHNOLOGY_HSPA, + [8] = MM_MODEM_ACCESS_TECHNOLOGY_LTE, + }; + + return (nsmod < G_N_ELEMENTS (simtech_act_to_mm_act_map) ? simtech_act_to_mm_act_map[nsmod] : MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); +} + +static void +simtech_tech_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemSimtech *self) +{ + guint simtech_act = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &simtech_act)) + return; + + mm_iface_modem_update_access_technologies ( + MM_IFACE_MODEM (self), + simtech_act_to_mm_act (simtech_act), + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); +} + +static void +simtech_signal_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemSimtech *self) +{ + guint quality = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &quality)) + return; + + if (quality != 99) + quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31; + else + quality = 0; + + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); +} + +static void +set_unsolicited_events_handlers (MMBroadbandModemSimtech *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* Access technology related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->cnsmod_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)simtech_tech_changed : NULL, + enable ? self : NULL, + NULL); + + /* Signal quality related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->csq_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)simtech_signal_changed : NULL, + enable ? self : NULL, + NULL); + } +} + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_SIMTECH (self), TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static void +parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own cleanup first */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_SIMTECH (self), FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Enable unsolicited events (3GPP interface) */ + +typedef enum { + ENABLE_UNSOLICITED_EVENTS_STEP_FIRST, + ENABLE_UNSOLICITED_EVENTS_STEP_PARENT, + ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_CNSMOD, + ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_CNSMOD, + ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_AUTOCSQ, + ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_AUTOCSQ, + ENABLE_UNSOLICITED_EVENTS_STEP_LAST, +} EnableUnsolicitedEventsStep; + +typedef struct { + EnableUnsolicitedEventsStep step; +} EnableUnsolicitedEventsContext; + +static gboolean +modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void enable_unsolicited_events_context_step (GTask *task); + +static void +autocsq_set_enabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + EnableUnsolicitedEventsContext *ctx; + GError *error = NULL; + gboolean csq_urcs_enabled = FALSE; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't enable automatic signal quality reporting: %s", error->message); + g_error_free (error); + } else + csq_urcs_enabled = TRUE; + + /* Disable access technology polling if we can use the +CSQ URCs */ + g_object_set (self, + MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, csq_urcs_enabled, + NULL); + + /* go to next step */ + ctx->step++; + enable_unsolicited_events_context_step (task); +} + +static void +autocsq_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemSimtech *self; + EnableUnsolicitedEventsContext *ctx; + + self = MM_BROADBAND_MODEM_SIMTECH (_self); + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (_self, res, NULL)) + self->priv->autocsq_support = FEATURE_NOT_SUPPORTED; + else + self->priv->autocsq_support = FEATURE_SUPPORTED; + + /* go to next step */ + ctx->step++; + enable_unsolicited_events_context_step (task); +} + +static void +cnsmod_set_enabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + EnableUnsolicitedEventsContext *ctx; + GError *error = NULL; + gboolean cnsmod_urcs_enabled = FALSE; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't enable automatic access technology reporting: %s", error->message); + g_error_free (error); + } else + cnsmod_urcs_enabled = TRUE; + + /* Disable access technology polling if we can use the +CNSMOD URCs */ + g_object_set (self, + MM_IFACE_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED, cnsmod_urcs_enabled, + NULL); + + /* go to next step */ + ctx->step++; + enable_unsolicited_events_context_step (task); +} + +static void +cnsmod_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemSimtech *self; + EnableUnsolicitedEventsContext *ctx; + + self = MM_BROADBAND_MODEM_SIMTECH (_self); + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (_self, res, NULL)) + self->priv->cnsmod_support = FEATURE_NOT_SUPPORTED; + else + self->priv->cnsmod_support = FEATURE_SUPPORTED; + + /* go to next step */ + ctx->step++; + enable_unsolicited_events_context_step (task); +} + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + EnableUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* go to next step */ + ctx->step++; + enable_unsolicited_events_context_step (task); +} + +static void +enable_unsolicited_events_context_step (GTask *task) +{ + MMBroadbandModemSimtech *self; + EnableUnsolicitedEventsContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case ENABLE_UNSOLICITED_EVENTS_STEP_FIRST: + ctx->step++; + /* fall through */ + + case ENABLE_UNSOLICITED_EVENTS_STEP_PARENT: + iface_modem_3gpp_parent->enable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + task); + return; + + case ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_CNSMOD: + if (self->priv->cnsmod_support == FEATURE_SUPPORT_UNKNOWN) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CNSMOD=?", + 3, + TRUE, + (GAsyncReadyCallback)cnsmod_test_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_CNSMOD: + if (self->priv->cnsmod_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + /* Autoreport +CNSMOD when it changes */ + "+CNSMOD=1", + 20, + FALSE, + (GAsyncReadyCallback)cnsmod_set_enabled_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_AUTOCSQ: + if (self->priv->autocsq_support == FEATURE_SUPPORT_UNKNOWN) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+AUTOCSQ=?", + 3, + TRUE, + (GAsyncReadyCallback)autocsq_test_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_AUTOCSQ: + if (self->priv->autocsq_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + /* Autoreport+ CSQ (first arg), and only report when it changes (second arg) */ + "+AUTOCSQ=1,1", + 20, + FALSE, + (GAsyncReadyCallback)autocsq_set_enabled_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLE_UNSOLICITED_EVENTS_STEP_LAST: + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnableUnsolicitedEventsContext *ctx; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_new (EnableUnsolicitedEventsContext, 1); + ctx->step = ENABLE_UNSOLICITED_EVENTS_STEP_FIRST; + g_task_set_task_data (task, ctx, g_free); + + enable_unsolicited_events_context_step (task); +} + +/*****************************************************************************/ +/* Disable unsolicited events (3GPP interface) */ + +typedef enum { + DISABLE_UNSOLICITED_EVENTS_STEP_FIRST, + DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_AUTOCSQ, + DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_CNSMOD, + DISABLE_UNSOLICITED_EVENTS_STEP_PARENT, + DISABLE_UNSOLICITED_EVENTS_STEP_LAST, +} DisableUnsolicitedEventsStep; + +typedef struct { + DisableUnsolicitedEventsStep step; +} DisableUnsolicitedEventsContext; + +static gboolean +modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void disable_unsolicited_events_context_step (GTask *task); + +static void +parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + DisableUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* go to next step */ + ctx->step++; + disable_unsolicited_events_context_step (task); +} + +static void +cnsmod_set_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + DisableUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't disable automatic access technology reporting: %s", error->message); + g_error_free (error); + } + + /* go to next step */ + ctx->step++; + disable_unsolicited_events_context_step (task); +} + +static void +autocsq_set_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + DisableUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't disable automatic signal quality reporting: %s", error->message); + g_error_free (error); + } + + /* go to next step */ + ctx->step++; + disable_unsolicited_events_context_step (task); +} + +static void +disable_unsolicited_events_context_step (GTask *task) +{ + MMBroadbandModemSimtech *self; + DisableUnsolicitedEventsContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case DISABLE_UNSOLICITED_EVENTS_STEP_FIRST: + ctx->step++; + /* fall through */ + + case DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_AUTOCSQ: + if (self->priv->autocsq_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+AUTOCSQ=0", + 20, + FALSE, + (GAsyncReadyCallback)autocsq_set_disabled_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_CNSMOD: + if (self->priv->cnsmod_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CNSMOD=0", + 20, + FALSE, + (GAsyncReadyCallback)cnsmod_set_disabled_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case DISABLE_UNSOLICITED_EVENTS_STEP_PARENT: + iface_modem_3gpp_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, + task); + return; + + case DISABLE_UNSOLICITED_EVENTS_STEP_LAST: + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DisableUnsolicitedEventsContext *ctx; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_new (DisableUnsolicitedEventsContext, 1); + ctx->step = DISABLE_UNSOLICITED_EVENTS_STEP_FIRST; + g_task_set_task_data (task, ctx, g_free); + + disable_unsolicited_events_context_step (task); +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + GError *inner_error = NULL; + gssize act; + + act = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + *access_technologies = (MMModemAccessTechnology) act; + *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; + return TRUE; +} + +static void +cnsmod_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response, *p; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + p = mm_strip_tag (response, "+CNSMOD:"); + if (p) + p = strchr (p, ','); + + if (!p || !isdigit (*(p + 1))) + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse the +CNSMOD response: '%s'", + response); + else + g_task_return_int (task, simtech_act_to_mm_act (atoi (p + 1))); + g_object_unref (task); +} + +static void +load_access_technologies (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemSimtech *self; + GTask *task; + + self = MM_BROADBAND_MODEM_SIMTECH (_self); + task = g_task_new (self, NULL, callback, user_data); + + /* Launch query only for 3GPP modems */ + if (!mm_iface_modem_is_3gpp (_self)) { + g_task_return_int (task, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); + g_object_unref (task); + return; + } + + g_assert (self->priv->cnsmod_support != FEATURE_SUPPORT_UNKNOWN); + if (self->priv->cnsmod_support == FEATURE_NOT_SUPPORTED) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Loading access technologies with +CNSMOD is not supported"); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "AT+CNSMOD?", + 3, + FALSE, + (GAsyncReadyCallback)cnsmod_query_ready, + task); +} + +/*****************************************************************************/ +/* Load signal quality (Modem interface) */ + +static guint +load_signal_quality_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + gssize value; + + value = g_task_propagate_int (G_TASK (res), error); + return value < 0 ? 0 : value; +} + +static void +csq_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response, *p; + GError *error = NULL; + gint quality; + gint ber; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Given that we may have enabled AUTOCSQ support, it is totally possible + * to get an empty string at this point, because the +CSQ reply may have + * been processed as an URC already. If we ever see this, we should not return + * an error, because that would reset the reported signal quality to 0 :/ + * So, in this case, return the last cached signal quality value. */ + if (!response[0]) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS, + "already refreshed via URCs"); + g_object_unref (task); + return; + } + + p = mm_strip_tag (response, "+CSQ:"); + if (sscanf (p, "%d, %d", &quality, &ber)) { + if (quality != 99) + quality = CLAMP (quality, 0, 31) * 100 / 31; + else + quality = 0; + g_task_return_int (task, quality); + g_object_unref (task); + return; + } + + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Could not parse signal quality results"); + g_object_unref (task); +} + +static void +load_signal_quality (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CSQ", + 3, + FALSE, + (GAsyncReadyCallback)csq_query_ready, + task); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMModemModeCombination mode; + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5); + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G, 2G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + /* 2G and 3G, 3G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +typedef struct { + MMModemMode allowed; + MMModemMode preferred; +} LoadCurrentModesResult; + +typedef struct { + gint acqord; + gint modepref; +} LoadCurrentModesContext; + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + LoadCurrentModesResult *result; + + result = g_task_propagate_pointer (G_TASK (res), error); + if (!result) + return FALSE; + + *allowed = result->allowed; + *preferred = result->preferred; + g_free (result); + return TRUE; +} + +static void +cnmp_query_ready (MMBroadbandModemSimtech *self, + GAsyncResult *res, + GTask *task) +{ + LoadCurrentModesContext *ctx; + LoadCurrentModesResult *result; + const gchar *response, *p; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + p = mm_strip_tag (response, "+CNMP:"); + if (!p) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse the mode preference response: '%s'", + response); + g_object_unref (task); + return; + } + + result = g_new (LoadCurrentModesResult, 1); + result->allowed = MM_MODEM_MODE_NONE; + result->preferred = MM_MODEM_MODE_NONE; + + ctx->modepref = atoi (p); + switch (ctx->modepref) { + case 2: + /* Automatic */ + switch (ctx->acqord) { + case 0: + result->allowed = MM_MODEM_MODE_ANY; + result->preferred = MM_MODEM_MODE_NONE; + break; + case 1: + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + result->preferred = MM_MODEM_MODE_2G; + break; + case 2: + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + result->preferred = MM_MODEM_MODE_3G; + break; + default: + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unknown acquisition order preference: '%d'", + ctx->acqord); + g_object_unref (task); + g_free (result); + return; + } + break; + + case 13: + /* GSM only */ + result->allowed = MM_MODEM_MODE_2G; + result->preferred = MM_MODEM_MODE_NONE; + break; + + case 14: + /* WCDMA only */ + result->allowed = MM_MODEM_MODE_3G; + result->preferred = MM_MODEM_MODE_NONE; + break; + + default: + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unknown mode preference: '%d'", + ctx->modepref); + g_object_unref (task); + g_free (result); + return; + } + + g_task_return_pointer (task, result, g_free); + g_object_unref (task); +} + +static void +cnaop_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LoadCurrentModesContext *ctx; + const gchar *response, *p; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + p = mm_strip_tag (response, "+CNAOP:"); + if (p) + ctx->acqord = atoi (p); + + if (ctx->acqord < 0 || ctx->acqord > 2) { + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse the acquisition order response: '%s'", + response); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CNMP?", + 3, + FALSE, + (GAsyncReadyCallback)cnmp_query_ready, + task); +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LoadCurrentModesContext *ctx; + + ctx = g_new (LoadCurrentModesContext, 1); + ctx->acqord = -1; + ctx->modepref = -1; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CNAOP?", + 3, + FALSE, + (GAsyncReadyCallback)cnaop_query_ready, + task); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +typedef struct { + guint nmp; /* mode preference */ + guint naop; /* acquisition order */ +} SetCurrentModesContext; + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cnaop_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +cnmp_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + SetCurrentModesContext *ctx; + GError *error = NULL; + gchar *command; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) { + /* Let the error be critical. */ + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + command = g_strdup_printf ("+CNAOP=%u", ctx->naop); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)cnaop_set_ready, + task); + g_free (command); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + SetCurrentModesContext *ctx; + gchar *command; + + /* Defaults: automatic search */ + ctx = g_new (SetCurrentModesContext, 1); + ctx->nmp = 2; + ctx->naop = 0; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE) { + /* defaults nmp and naop */ + } else if (allowed == MM_MODEM_MODE_2G) { + ctx->nmp = 13; + ctx->naop = 0; + } else if (allowed == MM_MODEM_MODE_3G) { + ctx->nmp = 14; + ctx->naop = 0; + } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { + /* default nmp */ + if (preferred == MM_MODEM_MODE_2G) + ctx->naop = 3; + else if (preferred == MM_MODEM_MODE_3G) + ctx->naop = 2; + else + /* default naop */ + ctx->naop = 0; + } else { + 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_object_unref (task); + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("+CNMP=%u", ctx->nmp); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)cnmp_set_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *self) +{ + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_simtech_parent_class)->setup_ports (self); + + /* Now reset the unsolicited messages we'll handle when enabled */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_SIMTECH (self), FALSE); +} + +/*****************************************************************************/ + +MMBroadbandModemSimtech * +mm_broadband_modem_simtech_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_SIMTECH, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + MM_BROADBAND_MODEM_INDICATORS_DISABLED, TRUE, + NULL); +} + +static void +mm_broadband_modem_simtech_init (MMBroadbandModemSimtech *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_SIMTECH, + MMBroadbandModemSimtechPrivate); + + self->priv->cnsmod_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->autocsq_support = FEATURE_SUPPORT_UNKNOWN; + + self->priv->cnsmod_regex = g_regex_new ("\\r\\n\\+CNSMOD:\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->csq_regex = g_regex_new ("\\r\\n\\+CSQ:\\s*(\\d+),(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemSimtech *self = MM_BROADBAND_MODEM_SIMTECH (object); + + g_regex_unref (self->priv->cnsmod_regex); + g_regex_unref (self->priv->csq_regex); + + G_OBJECT_CLASS (mm_broadband_modem_simtech_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->load_signal_quality = load_signal_quality; + iface->load_signal_quality_finish = load_signal_quality_finish; + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_simtech_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_simtech_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_simtech_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_simtech_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_simtech_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_simtech_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedSimtech *self) +{ + return iface_modem_location_parent; +} + +static void +iface_modem_voice_init (MMIfaceModemVoice *iface) +{ + iface_modem_voice_parent = g_type_interface_peek_parent (iface); + + iface->check_support = mm_shared_simtech_voice_check_support; + iface->check_support_finish = mm_shared_simtech_voice_check_support_finish; + iface->enable_unsolicited_events = mm_shared_simtech_voice_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = mm_shared_simtech_voice_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = mm_shared_simtech_voice_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = mm_shared_simtech_voice_disable_unsolicited_events_finish; + iface->setup_unsolicited_events = mm_shared_simtech_voice_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_simtech_voice_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_simtech_voice_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_simtech_voice_cleanup_unsolicited_events_finish; + iface->setup_in_call_audio_channel = mm_shared_simtech_voice_setup_in_call_audio_channel; + iface->setup_in_call_audio_channel_finish = mm_shared_simtech_voice_setup_in_call_audio_channel_finish; + iface->cleanup_in_call_audio_channel = mm_shared_simtech_voice_cleanup_in_call_audio_channel; + iface->cleanup_in_call_audio_channel_finish = mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish; + +} + +static MMIfaceModemVoice * +peek_parent_voice_interface (MMSharedSimtech *self) +{ + return iface_modem_voice_parent; +} + +static void +shared_simtech_init (MMSharedSimtech *iface) +{ + iface->peek_parent_location_interface = peek_parent_location_interface; + iface->peek_parent_voice_interface = peek_parent_voice_interface; +} + +static void +mm_broadband_modem_simtech_class_init (MMBroadbandModemSimtechClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemSimtechPrivate)); + + object_class->finalize = finalize; + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/simtech/mm-broadband-modem-simtech.h b/src/plugins/simtech/mm-broadband-modem-simtech.h new file mode 100644 index 00000000..a2b57fea --- /dev/null +++ b/src/plugins/simtech/mm-broadband-modem-simtech.h @@ -0,0 +1,51 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_SIMTECH_H +#define MM_BROADBAND_MODEM_SIMTECH_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_SIMTECH (mm_broadband_modem_simtech_get_type ()) +#define MM_BROADBAND_MODEM_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_SIMTECH, MMBroadbandModemSimtech)) +#define MM_BROADBAND_MODEM_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_SIMTECH, MMBroadbandModemSimtechClass)) +#define MM_IS_BROADBAND_MODEM_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_SIMTECH)) +#define MM_IS_BROADBAND_MODEM_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_SIMTECH)) +#define MM_BROADBAND_MODEM_SIMTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_SIMTECH, MMBroadbandModemSimtechClass)) + +typedef struct _MMBroadbandModemSimtech MMBroadbandModemSimtech; +typedef struct _MMBroadbandModemSimtechClass MMBroadbandModemSimtechClass; +typedef struct _MMBroadbandModemSimtechPrivate MMBroadbandModemSimtechPrivate; + +struct _MMBroadbandModemSimtech { + MMBroadbandModem parent; + MMBroadbandModemSimtechPrivate *priv; +}; + +struct _MMBroadbandModemSimtechClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_simtech_get_type (void); + +MMBroadbandModemSimtech *mm_broadband_modem_simtech_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_SIMTECH_H */ diff --git a/src/plugins/simtech/mm-modem-helpers-simtech.c b/src/plugins/simtech/mm-modem-helpers-simtech.c new file mode 100644 index 00000000..0403c145 --- /dev/null +++ b/src/plugins/simtech/mm-modem-helpers-simtech.c @@ -0,0 +1,209 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "ModemManager.h" +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> +#include "mm-errors-types.h" +#include "mm-modem-helpers-simtech.h" +#include "mm-modem-helpers.h" + + +/*****************************************************************************/ +/* +CLCC test parser + * + * Example (SIM7600E): + * AT+CLCC=? + * +CLCC: (0-1) + */ + +gboolean +mm_simtech_parse_clcc_test (const gchar *response, + gboolean *clcc_urcs_supported, + GError **error) +{ + g_assert (response); + + response = mm_strip_tag (response, "+CLCC:"); + + /* 3GPP specifies that the output of AT+CLCC=? should be just OK, so support + * that */ + if (!response[0]) { + *clcc_urcs_supported = FALSE; + return TRUE; + } + + /* As per 3GPP TS 27.007, the AT+CLCC command doesn't expect any argument, + * as it only is designed to report the current call list, nothing else. + * In the case of the Simtech plugin, though, we are going to support +CLCC + * URCs that can be enabled/disabled via AT+CLCC=1/0. We therefore need to + * detect whether this URC management is possible or not, for now with a + * simple check looking for the specific "(0-1)" string. + */ + if (!strncmp (response, "(0-1)", 5)) { + *clcc_urcs_supported = TRUE; + return TRUE; + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "unexpected +CLCC test response: '%s'", response); + return FALSE; +} + +/*****************************************************************************/ + +GRegex * +mm_simtech_get_clcc_urc_regex (void) +{ + return g_regex_new ("\\r\\n(\\+CLCC: .*\\r\\n)+", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +gboolean +mm_simtech_parse_clcc_list (const gchar *str, + gpointer log_object, + GList **out_list, + GError **error) +{ + /* Parse the URC contents as a plain +CLCC response, but make sure to skip first + * EOL in the string because the plain +CLCC response would never have that. + */ + return mm_3gpp_parse_clcc_response (mm_strip_tag (str, "\r\n"), log_object, out_list, error); +} + +void +mm_simtech_call_info_list_free (GList *call_info_list) +{ + mm_3gpp_call_info_list_free (call_info_list); +} + +/*****************************************************************************/ + +/* + * <CR><LF>VOICE CALL: BEGIN<CR><LF> + * <CR><LF>VOICE CALL: END: 000041<CR><LF> + */ +GRegex * +mm_simtech_get_voice_call_urc_regex (void) +{ + return g_regex_new ("\\r\\nVOICE CALL:\\s*([A-Z]+)(?::\\s*(\\d+))?\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +gboolean +mm_simtech_parse_voice_call_urc (GMatchInfo *match_info, + gboolean *start_or_stop, + guint *duration, + GError **error) +{ + GError *inner_error = NULL; + gchar *str; + + str = mm_get_string_unquoted_from_match_info (match_info, 1); + if (!str) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't read voice call URC action"); + goto out; + } + + if (g_strcmp0 (str, "BEGIN") == 0) { + *start_or_stop = TRUE; + *duration = 0; + goto out; + } + + if (g_strcmp0 (str, "END") == 0) { + *start_or_stop = FALSE; + if (!mm_get_uint_from_match_info (match_info, 2, duration)) + *duration = 0; + goto out; + } + + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown voice call URC action: %s", str); + +out: + g_free (str); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +/* + * <CR><LF>MISSED_CALL: 11:01AM 07712345678<CR><LF> + */ +GRegex * +mm_simtech_get_missed_call_urc_regex (void) +{ + return g_regex_new ("\\r\\nMISSED_CALL:\\s*(.+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +gboolean +mm_simtech_parse_missed_call_urc (GMatchInfo *match_info, + gchar **details, + GError **error) +{ + gchar *str; + + str = mm_get_string_unquoted_from_match_info (match_info, 1); + if (!str) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't read missed call URC details"); + return FALSE; + } + + *details = str; + return TRUE; +} + +/*****************************************************************************/ + +/* + * Using TWO <CR> instead of one... + * <CR><CR><LF>+CRING: VOICE<CR><CR><LF> + */ +GRegex * +mm_simtech_get_cring_urc_regex (void) +{ + return g_regex_new ("(?:\\r)+\\n\\+CRING:\\s*(\\S+)(?:\\r)+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +/*****************************************************************************/ + +/* + * <CR><CR><LF>+RXDTMF: 8<CR><CR><LF> + * <CR><CR><LF>+RXDTMF: *<CR><CR><LF> + * <CR><CR><LF>+RXDTMF: 7<CR><CR><LF> + * + * Note! using TWO <CR> instead of one... + */ +GRegex * +mm_simtech_get_rxdtmf_urc_regex (void) +{ + return g_regex_new ("(?:\\r)+\\n\\+RXDTMF:\\s*([0-9A-D\\*\\#])(?:\\r)+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} diff --git a/src/plugins/simtech/mm-modem-helpers-simtech.h b/src/plugins/simtech/mm-modem-helpers-simtech.h new file mode 100644 index 00000000..1949d2e7 --- /dev/null +++ b/src/plugins/simtech/mm-modem-helpers-simtech.h @@ -0,0 +1,67 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_MODEM_HELPERS_SIMTECH_H +#define MM_MODEM_HELPERS_SIMTECH_H + +#include <glib.h> + +#include <ModemManager.h> +#include <mm-base-bearer.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +/*****************************************************************************/ +/* +CLCC URC helpers */ + +gboolean mm_simtech_parse_clcc_test (const gchar *response, + gboolean *clcc_urcs_supported, + GError **error); + +GRegex *mm_simtech_get_clcc_urc_regex (void); +gboolean mm_simtech_parse_clcc_list (const gchar *str, + gpointer log_object, + GList **out_list, + GError **error); +void mm_simtech_call_info_list_free (GList *call_info_list); + +/*****************************************************************************/ +/* VOICE CALL URC helpers */ + +GRegex *mm_simtech_get_voice_call_urc_regex (void); +gboolean mm_simtech_parse_voice_call_urc (GMatchInfo *match_info, + gboolean *start_or_stop, + guint *duration, + GError **error); + +/*****************************************************************************/ +/* MISSED_CALL URC helpers */ + +GRegex *mm_simtech_get_missed_call_urc_regex (void); +gboolean mm_simtech_parse_missed_call_urc (GMatchInfo *match_info, + gchar **details, + GError **error); + +/*****************************************************************************/ +/* Non-standard CRING URC helpers */ + +GRegex *mm_simtech_get_cring_urc_regex (void); + +/*****************************************************************************/ +/* +RXDTMF URC helpers */ + +GRegex *mm_simtech_get_rxdtmf_urc_regex (void); + +#endif /* MM_MODEM_HELPERS_SIMTECH_H */ diff --git a/src/plugins/simtech/mm-plugin-simtech.c b/src/plugins/simtech/mm-plugin-simtech.c new file mode 100644 index 00000000..9b4f377e --- /dev/null +++ b/src/plugins/simtech/mm-plugin-simtech.c @@ -0,0 +1,98 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-simtech.h" +#include "mm-broadband-modem-simtech.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi-simtech.h" +#endif + +G_DEFINE_TYPE (MMPluginSimtech, mm_plugin_simtech, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered SimTech modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_simtech_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_simtech_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x1e0e, /* A-Link (for now) */ + 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_SIMTECH, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + NULL)); +} + +static void +mm_plugin_simtech_init (MMPluginSimtech *self) +{ +} + +static void +mm_plugin_simtech_class_init (MMPluginSimtechClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/simtech/mm-plugin-simtech.h b/src/plugins/simtech/mm-plugin-simtech.h new file mode 100644 index 00000000..eab8630c --- /dev/null +++ b/src/plugins/simtech/mm-plugin-simtech.h @@ -0,0 +1,42 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_SIMTECH_H +#define MM_PLUGIN_SIMTECH_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_SIMTECH (mm_plugin_simtech_get_type ()) +#define MM_PLUGIN_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtech)) +#define MM_PLUGIN_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtechClass)) +#define MM_IS_PLUGIN_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_SIMTECH)) +#define MM_IS_PLUGIN_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_SIMTECH)) +#define MM_PLUGIN_SIMTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtechClass)) + +typedef struct { + MMPlugin parent; +} MMPluginSimtech; + +typedef struct { + MMPluginClass parent; +} MMPluginSimtechClass; + +GType mm_plugin_simtech_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_SIMTECH_H */ diff --git a/src/plugins/simtech/mm-shared-simtech.c b/src/plugins/simtech/mm-shared-simtech.c new file mode 100644 index 00000000..99c2346e --- /dev/null +++ b/src/plugins/simtech/mm-shared-simtech.c @@ -0,0 +1,1261 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-voice.h" +#include "mm-iface-modem-location.h" +#include "mm-base-modem.h" +#include "mm-base-modem-at.h" +#include "mm-shared-simtech.h" +#include "mm-modem-helpers-simtech.h" + +/*****************************************************************************/ +/* Private data context */ + +#define PRIVATE_TAG "shared-simtech-private-tag" +static GQuark private_quark; + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED, +} FeatureSupport; + +typedef struct { + /* location */ + MMIfaceModemLocation *iface_modem_location_parent; + MMModemLocationSource supported_sources; + MMModemLocationSource enabled_sources; + FeatureSupport cgps_support; + /* voice */ + MMIfaceModemVoice *iface_modem_voice_parent; + FeatureSupport cpcmreg_support; + FeatureSupport clcc_urc_support; + GRegex *clcc_urc_regex; + GRegex *voice_call_regex; + GRegex *missed_call_regex; + GRegex *cring_regex; + GRegex *rxdtmf_regex; +} Private; + +static void +private_free (Private *ctx) +{ + g_regex_unref (ctx->rxdtmf_regex); + g_regex_unref (ctx->cring_regex); + g_regex_unref (ctx->missed_call_regex); + g_regex_unref (ctx->voice_call_regex); + g_regex_unref (ctx->clcc_urc_regex); + g_slice_free (Private, ctx); +} + +static Private * +get_private (MMSharedSimtech *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); + priv->supported_sources = MM_MODEM_LOCATION_SOURCE_NONE; + priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE; + priv->cgps_support = FEATURE_SUPPORT_UNKNOWN; + priv->cpcmreg_support = FEATURE_SUPPORT_UNKNOWN; + priv->clcc_urc_support = FEATURE_SUPPORT_UNKNOWN; + priv->clcc_urc_regex = mm_simtech_get_clcc_urc_regex (); + priv->voice_call_regex = mm_simtech_get_voice_call_urc_regex (); + priv->missed_call_regex = mm_simtech_get_missed_call_urc_regex (); + priv->cring_regex = mm_simtech_get_cring_urc_regex (); + priv->rxdtmf_regex = mm_simtech_get_rxdtmf_urc_regex (); + + /* Setup parent class' MMIfaceModemLocation and MMIfaceModemVoice */ + + g_assert (MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_location_interface); + priv->iface_modem_location_parent = MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_location_interface (self); + + g_assert (MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_voice_interface); + priv->iface_modem_voice_parent = MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_voice_interface (self); + + g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); + } + + return priv; +} + +/*****************************************************************************/ +/* GPS trace received */ + +static void +trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +/*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +MMModemLocationSource +mm_shared_simtech_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize aux; + + aux = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource) aux; +} + +static void probe_gps_features (GTask *task); + +static void +cgps_test_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!mm_base_modem_at_command_finish (self, res, NULL)) + priv->cgps_support = FEATURE_NOT_SUPPORTED; + else + priv->cgps_support = FEATURE_SUPPORTED; + + probe_gps_features (task); +} + +static void +probe_gps_features (GTask *task) +{ + MMSharedSimtech *self; + MMModemLocationSource sources; + Private *priv; + + self = MM_SHARED_SIMTECH (g_task_get_source_object (task)); + priv = get_private (self); + + /* Need to check if CGPS supported... */ + if (priv->cgps_support == FEATURE_SUPPORT_UNKNOWN) { + mm_base_modem_at_command (MM_BASE_MODEM (self), "+CGPS=?", 3, TRUE, (GAsyncReadyCallback) cgps_test_ready, task); + return; + } + + /* All GPS features probed */ + + /* Recover parent sources */ + sources = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + if (priv->cgps_support == FEATURE_SUPPORTED) { + mm_obj_dbg (self, "GPS commands supported: GPS capabilities enabled"); + + /* We only flag as supported by this implementation those sources not already + * supported by the parent implementation */ + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) + priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA; + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW)) + priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW; + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) + priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED; + + sources |= priv->supported_sources; + + /* Add handler for the NMEA traces in the GPS data port */ + mm_port_serial_gps_add_trace_handler (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)), + (MMPortSerialGpsTraceFn)trace_received, + self, + NULL); + } else + mm_obj_dbg (self, "no GPS command supported: no GPS capabilities"); + + g_task_return_int (task, (gssize) sources); + g_object_unref (task); +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Now our own check. If we don't have any GPS port, we're done */ + if (!mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) { + mm_obj_dbg (self, "no GPS data port found: no GPS capabilities"); + g_task_return_int (task, sources); + g_object_unref (task); + return; + } + + /* Cache sources supported by the parent */ + g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL); + + /* Probe all GPS features */ + probe_gps_features (task); +} + +void +mm_shared_simtech_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + priv = get_private (MM_SHARED_SIMTECH (self)); + task = g_task_new (self, NULL, callback, user_data); + + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->load_capabilities); + g_assert (priv->iface_modem_location_parent->load_capabilities_finish); + + priv->iface_modem_location_parent->load_capabilities (self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + task); +} + +/*****************************************************************************/ +/* Disable location gathering (Location interface) */ + +gboolean +mm_shared_simtech_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +disable_cgps_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource source; + Private *priv; + GError *error = NULL; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + mm_base_modem_at_command_finish (self, res, &error); + + /* Only use the GPS port in NMEA/RAW setups */ + source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task)); + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMPortSerialGps *gps_port; + + /* Even if we get an error here, we try to close the GPS port */ + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + if (error) + g_task_return_error (task, error); + else { + priv->enabled_sources &= ~source; + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +parent_disable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + g_assert (priv->iface_modem_location_parent); + if (!priv->iface_modem_location_parent->disable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_simtech_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMModemLocationSource enabled_sources; + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_location_parent); + + /* Only consider request if it applies to one of the sources we are + * supporting, otherwise run parent disable */ + if (!(priv->supported_sources & source)) { + /* If disabling implemented by the parent, run it. */ + if (priv->iface_modem_location_parent->disable_location_gathering && + priv->iface_modem_location_parent->disable_location_gathering_finish) { + priv->iface_modem_location_parent->disable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_disable_location_gathering_ready, + task); + return; + } + /* Otherwise, we're done */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* We only expect GPS sources here */ + g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)); + + /* Flag as disabled to see how many others we would have left enabled */ + enabled_sources = priv->enabled_sources; + enabled_sources &= ~source; + + /* If there are still GPS-related sources enabled, do nothing else */ + if (enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + priv->enabled_sources &= ~source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Stop GPS engine if all GPS-related sources are disabled */ + g_assert (priv->cgps_support == FEATURE_SUPPORTED); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CGPS=0", + 10, + FALSE, + (GAsyncReadyCallback) disable_cgps_ready, + task); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +gboolean +mm_shared_simtech_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +enable_cgps_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource source; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Only use the GPS port in NMEA/RAW setups */ + source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task)); + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMPortSerialGps *gps_port; + + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (!gps_port || !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't open raw GPS serial port"); + g_object_unref (task); + return; + } + } + + priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + g_assert (priv->iface_modem_location_parent); + if (!priv->iface_modem_location_parent->enable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_simtech_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->enable_location_gathering); + g_assert (priv->iface_modem_location_parent->enable_location_gathering_finish); + + /* Only consider request if it applies to one of the sources we are + * supporting, otherwise run parent enable */ + if (!(priv->supported_sources & source)) { + priv->iface_modem_location_parent->enable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); + return; + } + + /* We only expect GPS sources here */ + g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)); + + /* If GPS already started, store new flag and we're done */ + if (priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + g_assert (priv->cgps_support == FEATURE_SUPPORTED); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CGPS=1,1", + 10, + FALSE, + (GAsyncReadyCallback) enable_cgps_ready, + task); +} + +/*****************************************************************************/ +/* Common enable/disable voice unsolicited events */ + +typedef struct { + gboolean enable; + MMPortSerialAt *primary; + MMPortSerialAt *secondary; + gchar *clcc_command; + gboolean clcc_primary_done; + gboolean clcc_secondary_done; +} VoiceUnsolicitedEventsContext; + +static void +voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx) +{ + g_clear_object (&ctx->secondary); + g_clear_object (&ctx->primary); + g_free (ctx->clcc_command); + g_slice_free (VoiceUnsolicitedEventsContext, ctx); +} + +static gboolean +common_voice_enable_disable_unsolicited_events_finish (MMSharedSimtech *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void run_voice_enable_disable_unsolicited_events (GTask *task); + +static void +clcc_command_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + VoiceUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't %s +CLCC reporting: '%s'", + ctx->enable ? "enable" : "disable", + error->message); + g_error_free (error); + } + + /* Continue on next port */ + run_voice_enable_disable_unsolicited_events (task); +} + +static void +run_voice_enable_disable_unsolicited_events (GTask *task) +{ + MMSharedSimtech *self; + Private *priv; + VoiceUnsolicitedEventsContext *ctx; + MMPortSerialAt *port = NULL; + + self = MM_SHARED_SIMTECH (g_task_get_source_object (task)); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + /* If +CLCC URCs not supported, we're done */ + if (priv->clcc_urc_support == FEATURE_NOT_SUPPORTED) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (!ctx->clcc_primary_done && ctx->primary) { + mm_obj_dbg (self, "%s +CLCC extended list of current calls reporting in primary port...", + ctx->enable ? "enabling" : "disabling"); + ctx->clcc_primary_done = TRUE; + port = ctx->primary; + } else if (!ctx->clcc_secondary_done && ctx->secondary) { + mm_obj_dbg (self, "%s +CLCC extended list of current calls reporting in secondary port...", + ctx->enable ? "enabling" : "disabling"); + ctx->clcc_secondary_done = TRUE; + port = ctx->secondary; + } + + if (port) { + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + port, + ctx->clcc_command, + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)clcc_command_ready, + task); + return; + } + + /* Fully done now */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +common_voice_enable_disable_unsolicited_events (MMSharedSimtech *self, + gboolean enable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + VoiceUnsolicitedEventsContext *ctx; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_slice_new0 (VoiceUnsolicitedEventsContext); + ctx->enable = enable; + if (enable) + ctx->clcc_command = g_strdup ("+CLCC=1"); + else + ctx->clcc_command = g_strdup ("+CLCC=0"); + ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); + ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); + g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free); + + run_voice_enable_disable_unsolicited_events (task); +} + +/*****************************************************************************/ +/* Disable unsolicited events (Voice interface) */ + +gboolean +mm_shared_simtech_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!priv->iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't disable parent voice unsolicited events: %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +voice_disable_unsolicited_events_ready (MMSharedSimtech *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + GError *error = NULL; + + if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't disable Simtech-specific voice unsolicited events: %s", error->message); + g_error_free (error); + } + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events_finish); + + /* Chain up parent's disable */ + priv->iface_modem_voice_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_VOICE (self), + (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready, + task); +} + +void +mm_shared_simtech_voice_disable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* our own disabling first */ + common_voice_enable_disable_unsolicited_events (MM_SHARED_SIMTECH (self), + FALSE, + (GAsyncReadyCallback) voice_disable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Enable unsolicited events (Voice interface) */ + +gboolean +mm_shared_simtech_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +voice_enable_unsolicited_events_ready (MMSharedSimtech *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't enable Simtech-specific voice unsolicited events: %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!priv->iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't enable parent voice unsolicited events: %s", error->message); + g_error_free (error); + } + + /* our own enabling next */ + common_voice_enable_disable_unsolicited_events (MM_SHARED_SIMTECH (self), + TRUE, + (GAsyncReadyCallback) voice_enable_unsolicited_events_ready, + task); +} + +void +mm_shared_simtech_voice_enable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events_finish); + + /* chain up parent's enable first */ + priv->iface_modem_voice_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Common setup/cleanup voice unsolicited events */ + +static void +clcc_urc_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMSharedSimtech *self) +{ + gchar *full; + GError *error = NULL; + GList *call_info_list = NULL; + + full = g_match_info_fetch (match_info, 0); + + if (!mm_simtech_parse_clcc_list (full, self, &call_info_list, &error)) { + mm_obj_warn (self, "couldn't parse +CLCC list in URC: %s", error->message); + g_error_free (error); + } else + mm_iface_modem_voice_report_all_calls (MM_IFACE_MODEM_VOICE (self), call_info_list); + + mm_simtech_call_info_list_free (call_info_list); + g_free (full); +} + +static void +missed_call_urc_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMSharedSimtech *self) +{ + GError *error = NULL; + gchar *details = NULL; + + if (!mm_simtech_parse_missed_call_urc (match_info, &details, &error)) { + mm_obj_warn (self, "couldn't parse missed call URC: %s", error->message); + g_error_free (error); + return; + } + + mm_obj_dbg (self, "missed call reported: %s", details); + g_free (details); +} + +static void +voice_call_urc_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMSharedSimtech *self) +{ + GError *error = NULL; + gboolean start_or_stop = FALSE; /* start = TRUE, stop = FALSE */ + guint duration = 0; + + if (!mm_simtech_parse_voice_call_urc (match_info, &start_or_stop, &duration, &error)) { + mm_obj_warn (self, "couldn't parse voice call URC: %s", error->message); + g_error_free (error); + return; + } + + if (start_or_stop) { + mm_obj_dbg (self, "voice call started"); + return; + } + + if (duration) { + mm_obj_dbg (self, "voice call finished (duration: %us)", duration); + return; + } + + mm_obj_dbg (self, "voice call finished"); +} + +static void +cring_urc_received (MMPortSerialAt *port, + GMatchInfo *info, + MMSharedSimtech *self) +{ + MMCallInfo call_info; + g_autofree gchar *str = NULL; + + /* We could have "VOICE" or "DATA". Now consider only "VOICE" */ + str = mm_get_string_unquoted_from_match_info (info, 1); + mm_obj_dbg (self, "ringing (%s)", str); + + call_info.index = 0; + call_info.direction = MM_CALL_DIRECTION_INCOMING; + call_info.state = MM_CALL_STATE_RINGING_IN; + call_info.number = NULL; + + mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); +} + +static void +rxdtmf_urc_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMSharedSimtech *self) +{ + g_autofree gchar *dtmf = NULL; + + dtmf = g_match_info_fetch (match_info, 1); + mm_obj_dbg (self, "received DTMF: %s", dtmf); + /* call index unknown */ + mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf); +} + +static void +common_voice_setup_cleanup_unsolicited_events (MMSharedSimtech *self, + gboolean enable) +{ + Private *priv; + MMPortSerialAt *ports[2]; + guint i; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + if (priv->clcc_urc_support == FEATURE_SUPPORTED) + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->clcc_urc_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)clcc_urc_received : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->voice_call_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)voice_call_urc_received : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->missed_call_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)missed_call_urc_received : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->cring_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)cring_urc_received : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->rxdtmf_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)rxdtmf_urc_received : NULL, + enable ? self : NULL, + NULL); + } +} + +/*****************************************************************************/ +/* Cleanup unsolicited events (Voice interface) */ + +gboolean +mm_shared_simtech_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't cleanup parent voice unsolicited events: %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_simtech_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish); + + /* our own cleanup first */ + common_voice_setup_cleanup_unsolicited_events (MM_SHARED_SIMTECH (self), FALSE); + + /* Chain up parent's cleanup */ + priv->iface_modem_voice_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Setup unsolicited events (Voice interface) */ + +gboolean +mm_shared_simtech_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!priv->iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't setup parent voice unsolicited events: %s", error->message); + g_error_free (error); + } + + /* our own setup next */ + common_voice_setup_cleanup_unsolicited_events (MM_SHARED_SIMTECH (self), TRUE); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_simtech_voice_setup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events_finish); + + /* chain up parent's setup first */ + priv->iface_modem_voice_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* In-call audio channel setup/cleanup */ + +gboolean +mm_shared_simtech_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + MMPort **audio_port, /* optional */ + MMCallAudioFormat **audio_format, /* optional */ + GError **error) +{ + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!g_task_propagate_boolean (G_TASK (res), error)) + return FALSE; + + if (audio_format) + *audio_format = NULL; + + if (audio_port) { + if (priv->cpcmreg_support == FEATURE_SUPPORTED) + *audio_port = MM_PORT (mm_base_modem_get_port_audio (MM_BASE_MODEM (self))); + else + *audio_port = NULL; + } + + return TRUE; +} + +gboolean +mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cpcmreg_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +common_setup_cleanup_in_call_audio_channel (MMSharedSimtech *self, + gboolean setup, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + task = g_task_new (self, NULL, callback, user_data); + + /* Do nothing if CPCMREG isn't supported */ + if (priv->cpcmreg_support != FEATURE_SUPPORTED) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + setup ? "+CPCMREG=1" : "+CPCMREG=0", + 3, + FALSE, + (GAsyncReadyCallback) cpcmreg_set_ready, + task); +} + +void +mm_shared_simtech_voice_setup_in_call_audio_channel (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_in_call_audio_channel (MM_SHARED_SIMTECH (self), TRUE, callback, user_data); +} + +void +mm_shared_simtech_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_in_call_audio_channel (MM_SHARED_SIMTECH (self), FALSE, callback, user_data); +} + +/*****************************************************************************/ +/* Check if Voice supported (Voice interface) */ + +gboolean +mm_shared_simtech_voice_check_support_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cpcmreg_format_check_ready (MMBroadbandModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + priv->cpcmreg_support = (mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ? + FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED); + mm_obj_dbg (self, "modem %s USB audio control", (priv->cpcmreg_support == FEATURE_SUPPORTED) ? "supports" : "doesn't support"); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +clcc_format_check_ready (MMBroadbandModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + GError *error = NULL; + const gchar *response; + gboolean clcc_urc_supported = FALSE; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL); + if (response && !mm_simtech_parse_clcc_test (response, &clcc_urc_supported, &error)) { + mm_obj_dbg (self, "failed checking CLCC URC support: %s", error->message); + g_clear_error (&error); + } + + priv->clcc_urc_support = (clcc_urc_supported ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED); + mm_obj_dbg (self, "modem %s +CLCC URCs", (priv->clcc_urc_support == FEATURE_SUPPORTED) ? "supports" : "doesn't support"); + + /* If +CLCC URC supported we won't need polling in the parent */ + g_object_set (self, + MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, (priv->clcc_urc_support == FEATURE_SUPPORTED), + NULL); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CPCMREG=?", + 3, + TRUE, + (GAsyncReadyCallback) cpcmreg_format_check_ready, + task); +} + +static void +parent_voice_check_support_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + GError *error = NULL; + + priv = get_private (MM_SHARED_SIMTECH (self)); + if (!priv->iface_modem_voice_parent->check_support_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* voice is supported, check if +CLCC URCs are available */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CLCC=?", + 3, + TRUE, + (GAsyncReadyCallback) clcc_format_check_ready, + task); +} + +void +mm_shared_simtech_voice_check_support (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->check_support); + g_assert (priv->iface_modem_voice_parent->check_support_finish); + + /* chain up parent's setup first */ + priv->iface_modem_voice_parent->check_support ( + self, + (GAsyncReadyCallback)parent_voice_check_support_ready, + task); +} + +/*****************************************************************************/ + +static void +shared_simtech_init (gpointer g_iface) +{ +} + +GType +mm_shared_simtech_get_type (void) +{ + static GType shared_simtech_type = 0; + + if (!G_UNLIKELY (shared_simtech_type)) { + static const GTypeInfo info = { + sizeof (MMSharedSimtech), /* class_size */ + shared_simtech_init, /* base_init */ + NULL, /* base_finalize */ + }; + + shared_simtech_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedSimtech", &info, 0); + g_type_interface_add_prerequisite (shared_simtech_type, MM_TYPE_IFACE_MODEM_LOCATION); + } + + return shared_simtech_type; +} diff --git a/src/plugins/simtech/mm-shared-simtech.h b/src/plugins/simtech/mm-shared-simtech.h new file mode 100644 index 00000000..37a221ca --- /dev/null +++ b/src/plugins/simtech/mm-shared-simtech.h @@ -0,0 +1,129 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_SHARED_SIMTECH_H +#define MM_SHARED_SIMTECH_H + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" + +#define MM_TYPE_SHARED_SIMTECH (mm_shared_simtech_get_type ()) +#define MM_SHARED_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_SIMTECH, MMSharedSimtech)) +#define MM_IS_SHARED_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_SIMTECH)) +#define MM_SHARED_SIMTECH_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_SIMTECH, MMSharedSimtech)) + +typedef struct _MMSharedSimtech MMSharedSimtech; + +struct _MMSharedSimtech { + GTypeInterface g_iface; + + /* Peek location interface of the parent class of the object */ + MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedSimtech *self); + + /* Peek voice interface of the parent class of the object */ + MMIfaceModemVoice * (* peek_parent_voice_interface) (MMSharedSimtech *self); +}; + +GType mm_shared_simtech_get_type (void); + +/*****************************************************************************/ +/* Location interface */ + +void mm_shared_simtech_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMModemLocationSource mm_shared_simtech_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); + + +/*****************************************************************************/ +/* Voice interface */ + +void mm_shared_simtech_voice_check_support (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_check_support_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_voice_setup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_voice_enable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_voice_disable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_voice_setup_in_call_audio_channel (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + MMPort **audio_port, /* optional */ + MMCallAudioFormat **audio_format, /* optional */ + GError **error); +void mm_shared_simtech_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +#endif /* MM_SHARED_SIMTECH_H */ diff --git a/src/plugins/simtech/tests/test-modem-helpers-simtech.c b/src/plugins/simtech/tests/test-modem-helpers-simtech.c new file mode 100644 index 00000000..ba6532cc --- /dev/null +++ b/src/plugins/simtech/tests/test-modem-helpers-simtech.c @@ -0,0 +1,324 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-simtech.h" + +/*****************************************************************************/ +/* Test +CLCC URCs */ + +static void +common_test_clcc_urc (const gchar *urc, + const MMCallInfo *expected_call_info_list, + guint expected_call_info_list_size) +{ + g_autoptr(GRegex) clcc_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *str = NULL; + GError *error = NULL; + GList *call_info_list = NULL; + GList *l; + gboolean result; + + clcc_regex = mm_simtech_get_clcc_urc_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (clcc_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + /* read full matched content */ + str = g_match_info_fetch (match_info, 0); + g_assert (str); + + result = mm_simtech_parse_clcc_list (str, NULL, &call_info_list, &error); + g_assert_no_error (error); + g_assert (result); + + g_debug ("found %u calls", g_list_length (call_info_list)); + + if (expected_call_info_list) { + g_assert (call_info_list); + g_assert_cmpuint (g_list_length (call_info_list), ==, expected_call_info_list_size); + } else + g_assert (!call_info_list); + + for (l = call_info_list; l; l = g_list_next (l)) { + const MMCallInfo *call_info = (const MMCallInfo *)(l->data); + gboolean found = FALSE; + guint i; + + g_debug ("call at index %u: direction %s, state %s, number %s", + call_info->index, + mm_call_direction_get_string (call_info->direction), + mm_call_state_get_string (call_info->state), + call_info->number ? call_info->number : "n/a"); + + for (i = 0; !found && i < expected_call_info_list_size; i++) + found = ((call_info->index == expected_call_info_list[i].index) && + (call_info->direction == expected_call_info_list[i].direction) && + (call_info->state == expected_call_info_list[i].state) && + (g_strcmp0 (call_info->number, expected_call_info_list[i].number) == 0)); + + g_assert (found); + } + + mm_simtech_call_info_list_free (call_info_list); +} + +static void +test_clcc_urc_single (void) +{ + static const MMCallInfo expected_call_info_list[] = { + { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" } + }; + + const gchar *urc = + "\r\n+CLCC: 1,1,0,0,0,\"123456789\",161" + "\r\n"; + + common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list)); +} + +static void +test_clcc_urc_multiple (void) +{ + static const MMCallInfo expected_call_info_list[] = { + { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, NULL }, + { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" }, + { 3, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "987654321" }, + }; + + const gchar *urc = + "\r\n+CLCC: 1,1,0,0,0" /* number unknown */ + "\r\n+CLCC: 2,1,0,0,0,\"123456789\",161" + "\r\n+CLCC: 3,1,0,0,0,\"987654321\",161,\"Alice\"" + "\r\n"; + + common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list)); +} + +static void +test_clcc_urc_complex (void) +{ + static const MMCallInfo expected_call_info_list[] = { + { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" }, + { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_WAITING, (gchar *) "987654321" }, + }; + + const gchar *urc = + "\r\n^CIEV: 1,0" /* some different URC before our match */ + "\r\n+CLCC: 1,1,0,0,0,\"123456789\",161" + "\r\n+CLCC: 2,1,5,0,0,\"987654321\",161" + "\r\n^CIEV: 1,0" /* some different URC after our match */ + "\r\n"; + + common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list)); +} + +/*****************************************************************************/ + +static void +common_test_voice_call_urc (const gchar *urc, + gboolean expected_start_or_stop, + guint expected_duration) +{ + g_autoptr(GRegex) voice_call_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *error = NULL; + gboolean start_or_stop = FALSE; /* start = TRUE, stop = FALSE */ + guint duration = 0; + gboolean result; + + voice_call_regex = mm_simtech_get_voice_call_urc_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (voice_call_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + result = mm_simtech_parse_voice_call_urc (match_info, &start_or_stop, &duration, &error); + g_assert_no_error (error); + g_assert (result); + + g_assert_cmpuint (expected_start_or_stop, ==, start_or_stop); + g_assert_cmpuint (expected_duration, ==, duration); +} + +static void +test_voice_call_begin_urc (void) +{ + common_test_voice_call_urc ("\r\nVOICE CALL: BEGIN\r\n", TRUE, 0); +} + +static void +test_voice_call_end_urc (void) +{ + common_test_voice_call_urc ("\r\nVOICE CALL: END\r\n", FALSE, 0); +} + +static void +test_voice_call_end_duration_urc (void) +{ + common_test_voice_call_urc ("\r\nVOICE CALL: END: 000041\r\n", FALSE, 41); +} + +/*****************************************************************************/ + +static void +common_test_missed_call_urc (const gchar *urc, + const gchar *expected_details) +{ + g_autoptr(GRegex) missed_call_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *details = NULL; + GError *error = NULL; + gboolean result; + + missed_call_regex = mm_simtech_get_missed_call_urc_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (missed_call_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + result = mm_simtech_parse_missed_call_urc (match_info, &details, &error); + g_assert_no_error (error); + g_assert (result); + + g_assert_cmpstr (expected_details, ==, details); +} + +static void +test_missed_call_urc (void) +{ + common_test_missed_call_urc ("\r\nMISSED_CALL: 11:01AM 07712345678\r\n", "11:01AM 07712345678"); +} + +/*****************************************************************************/ + +static void +common_test_cring_urc (const gchar *urc, + const gchar *expected_type) +{ + g_autoptr(GRegex) cring_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *type = NULL; + GError *error = NULL; + gboolean result; + + cring_regex = mm_simtech_get_cring_urc_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (cring_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + type = g_match_info_fetch (match_info, 1); + g_assert (type); + + g_assert_cmpstr (type, ==, expected_type); +} + +static void +test_cring_urc_two_crs (void) +{ + common_test_cring_urc ("\r\r\n+CRING: VOICE\r\r\n", "VOICE"); +} + +static void +test_cring_urc_one_cr (void) +{ + common_test_cring_urc ("\r\n+CRING: VOICE\r\n", "VOICE"); +} + +/*****************************************************************************/ + +static void +common_test_rxdtmf_urc (const gchar *urc, + const gchar *expected_str) +{ + g_autoptr(GRegex) rxdtmf_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *type = NULL; + GError *error = NULL; + gboolean result; + + rxdtmf_regex = mm_simtech_get_rxdtmf_urc_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (rxdtmf_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + type = g_match_info_fetch (match_info, 1); + g_assert (type); + + g_assert_cmpstr (type, ==, expected_str); +} + +static void +test_rxdtmf_urc_two_crs (void) +{ + common_test_rxdtmf_urc ("\r\r\n+RXDTMF: 8\r\r\n", "8"); + common_test_rxdtmf_urc ("\r\r\n+RXDTMF: *\r\r\n", "*"); + common_test_rxdtmf_urc ("\r\r\n+RXDTMF: #\r\r\n", "#"); + common_test_rxdtmf_urc ("\r\r\n+RXDTMF: A\r\r\n", "A"); +} + +static void +test_rxdtmf_urc_one_cr (void) +{ + common_test_rxdtmf_urc ("\r\n+RXDTMF: 8\r\n", "8"); + common_test_rxdtmf_urc ("\r\n+RXDTMF: *\r\n", "*"); + common_test_rxdtmf_urc ("\r\n+RXDTMF: #\r\n", "#"); + common_test_rxdtmf_urc ("\r\n+RXDTMF: A\r\n", "A"); +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/simtech/clcc/urc/single", test_clcc_urc_single); + g_test_add_func ("/MM/simtech/clcc/urc/multiple", test_clcc_urc_multiple); + g_test_add_func ("/MM/simtech/clcc/urc/complex", test_clcc_urc_complex); + + g_test_add_func ("/MM/simtech/voicecall/urc/begin", test_voice_call_begin_urc); + g_test_add_func ("/MM/simtech/voicecall/urc/end", test_voice_call_end_urc); + g_test_add_func ("/MM/simtech/voicecall/urc/end-duration", test_voice_call_end_duration_urc); + + g_test_add_func ("/MM/simtech/missedcall/urc", test_missed_call_urc); + + g_test_add_func ("/MM/simtech/cring/urc/two-crs", test_cring_urc_two_crs); + g_test_add_func ("/MM/simtech/cring/urc/one-cr", test_cring_urc_one_cr); + + g_test_add_func ("/MM/simtech/rxdtmf/urc/two-crs", test_rxdtmf_urc_two_crs); + g_test_add_func ("/MM/simtech/rxdtmf/urc/one-cr", test_rxdtmf_urc_one_cr); + + return g_test_run (); +} diff --git a/src/plugins/symbol.map b/src/plugins/symbol.map new file mode 100644 index 00000000..b2c9f9cf --- /dev/null +++ b/src/plugins/symbol.map @@ -0,0 +1,8 @@ +{ +global: + mm_plugin_major_version*; + mm_plugin_minor_version*; + mm_plugin_create*; +local: + *; +}; diff --git a/src/plugins/telit/77-mm-telit-port-types.rules b/src/plugins/telit/77-mm-telit-port-types.rules new file mode 100644 index 00000000..b9439ffc --- /dev/null +++ b/src/plugins/telit/77-mm-telit-port-types.rules @@ -0,0 +1,146 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_telit_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1bc7", GOTO="mm_telit_generic" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="8087", GOTO="mm_telit_intel" +GOTO="mm_telit_end" + +LABEL="mm_telit_generic" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# UC864-E, UC864-E-AUTO, UC864-K, UC864-WD, UC864-WDU +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1003", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1003", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# UC864-G +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1004", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1004", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1004", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# CC864-DUAL +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1005", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1005", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1005", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# CC864-SINGLE, CC864-KPS +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1006", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1006", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# DE910-DUAL +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +# CE910-DUAL +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1011", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +# LE910C1-EUX +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# LE910C1-EUX (ECM composition) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# LE922, LM9x0 +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" + +# LE922, LM9x0 (MBIM composition) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1" + +# FN980 +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" + +# FN980 (MBIM composition) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1" + +# LN920 +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" + +# LN920 (MBIM composition) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1" + +# LE910C1 with default usb cfg +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" + +# LE910C1 (MBIM) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1" + +# ME910C1 +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# MEx10G1 +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# LE910S1 (RNDIS) +# The following port is ignored since it's a diagnostic port for collecting proprietary modem traces (not QCDM) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7010", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7010", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7010", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +# LE910S1 (ECM) +# The following port is ignored since it's a diagnostic port for collecting proprietary modem traces (not QCDM) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +# LM940/960 initial port delay +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{ID_MM_TELIT_PORT_DELAY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{ID_MM_TELIT_PORT_DELAY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1042", ENV{ID_MM_TELIT_PORT_DELAY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1043", ENV{ID_MM_TELIT_PORT_DELAY}="1" + +# FN980 initial port delay +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{ID_MM_TELIT_PORT_DELAY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{ID_MM_TELIT_PORT_DELAY}="1" + +# LN920 initial port delay +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{ID_MM_TELIT_PORT_DELAY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{ID_MM_TELIT_PORT_DELAY}="1" + +GOTO="mm_telit_end" + +LABEL="mm_telit_intel" + +# Telit LN930, generic Intel vid:pid in MBIM mode +ATTRS{idVendor}=="8087", ATTRS{idProduct}=="0911", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1" + +LABEL="mm_telit_end" diff --git a/src/plugins/telit/mm-broadband-modem-mbim-telit.c b/src/plugins/telit/mm-broadband-modem-mbim-telit.c new file mode 100644 index 00000000..8437c841 --- /dev/null +++ b/src/plugins/telit/mm-broadband-modem-mbim-telit.c @@ -0,0 +1,242 @@ +/* -*- 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 <dnlplm@gmail.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-iface-modem.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-mbim-telit.h" +#include "mm-modem-helpers-telit.h" +#include "mm-shared-telit.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void shared_telit_init (MMSharedTelit *iface); + +static MMIfaceModem *iface_modem_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimTelit, mm_broadband_modem_mbim_telit, MM_TYPE_BROADBAND_MODEM_MBIM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_TELIT, shared_telit_init)) + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return (GArray *) g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + MMModemModeCombination modes_combination; + MMModemMode modes_mask = MM_MODEM_MODE_NONE; + const gchar *response; + GArray *modes; + GArray *all; + GArray *combinations; + GArray *filtered; + GError *error = NULL; + MMSharedTelit *shared = MM_SHARED_TELIT (self); + guint i; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) { + g_prefix_error (&error, "generic query of supported 3GPP networks with WS46=? failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + modes = mm_3gpp_parse_ws46_test_response (response, self, &error); + if (!modes) { + g_prefix_error (&error, "parsing WS46=? response failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + for (i = 0; i < modes->len; i++) { + MMModemMode mode; + g_autofree gchar *str = NULL; + + mode = g_array_index (modes, MMModemMode, i); + + modes_mask |= mode; + + str = mm_modem_mode_build_string_from_mask (mode); + mm_obj_dbg (self, "device allows (3GPP) mode combination: %s", str); + } + + g_array_unref (modes); + + /* Build a mask with all supported modes */ + all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1); + modes_combination.allowed = modes_mask; + modes_combination.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (all, modes_combination); + + /* Filter out those unsupported modes */ + combinations = mm_telit_build_modes_list(); + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + mm_shared_telit_store_supported_modes (shared, filtered); + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+WS46=?", + 3, + TRUE, /* allow caching, it's a test command */ + (GAsyncReadyCallback) load_supported_modes_ready, + task); +} + +/*****************************************************************************/ +/* Load revision (Modem interface) */ + +static gchar * +load_revision_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_revision_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + gchar *revision = NULL; + + revision = iface_modem_parent->load_revision_finish (self, res, &error); + if (!revision) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + mm_shared_telit_store_revision (MM_SHARED_TELIT (self), revision); + g_task_return_pointer (task, revision, g_free); + g_object_unref (task); +} + +static void +load_revision (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + /* Telit's custom revision loading (in telit/mm-shared) is AT-only and the + * MBIM modem might not have an AT port available, so we call the parent's + * load_revision and store the revision taken from the firmware info capabilities. */ + iface_modem_parent->load_revision ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_revision_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ + +MMBroadbandModemMbimTelit * +mm_broadband_modem_mbim_telit_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id, + guint16 subsystem_vendor_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_TELIT, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + MM_BASE_MODEM_SUBSYSTEM_VENDOR_ID, subsystem_vendor_id, + /* MBIM bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_mbim_telit_init (MMBroadbandModemMbimTelit *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->set_current_bands = mm_shared_telit_modem_set_current_bands; + iface->set_current_bands_finish = mm_shared_telit_modem_set_current_bands_finish; + iface->load_current_bands = mm_shared_telit_modem_load_current_bands; + iface->load_current_bands_finish = mm_shared_telit_modem_load_current_bands_finish; + iface->load_supported_bands = mm_shared_telit_modem_load_supported_bands; + iface->load_supported_bands_finish = mm_shared_telit_modem_load_supported_bands_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = mm_shared_telit_load_current_modes; + iface->load_current_modes_finish = mm_shared_telit_load_current_modes_finish; + iface->set_current_modes = mm_shared_telit_set_current_modes; + iface->set_current_modes_finish = mm_shared_telit_set_current_modes_finish; + iface->load_revision_finish = load_revision_finish; + iface->load_revision = load_revision; +} + +static MMIfaceModem * +peek_parent_modem_interface (MMSharedTelit *self) +{ + return iface_modem_parent; +} + +static void +shared_telit_init (MMSharedTelit *iface) +{ + iface->peek_parent_modem_interface = peek_parent_modem_interface; +} + +static void +mm_broadband_modem_mbim_telit_class_init (MMBroadbandModemMbimTelitClass *klass) +{ +} diff --git a/src/plugins/telit/mm-broadband-modem-mbim-telit.h b/src/plugins/telit/mm-broadband-modem-mbim-telit.h new file mode 100644 index 00000000..50c21e20 --- /dev/null +++ b/src/plugins/telit/mm-broadband-modem-mbim-telit.h @@ -0,0 +1,48 @@ +/* -*- 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 <dnlplm@gmail.com> + */ + +#ifndef MM_BROADBAND_MODEM_MBIM_TELIT_H +#define MM_BROADBAND_MODEM_MBIM_TELIT_H + +#include "mm-broadband-modem-mbim.h" + +#define MM_TYPE_BROADBAND_MODEM_MBIM_TELIT (mm_broadband_modem_mbim_telit_get_type ()) +#define MM_BROADBAND_MODEM_MBIM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT, MMBroadbandModemMbimTelit)) +#define MM_BROADBAND_MODEM_MBIM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT, MMBroadbandModemMbimTelitClass)) +#define MM_IS_BROADBAND_MODEM_MBIM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT)) +#define MM_IS_BROADBAND_MODEM_MBIM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT)) +#define MM_BROADBAND_MODEM_MBIM_TELIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT, MMBroadbandModemMbimTelitClass)) + +typedef struct _MMBroadbandModemMbimTelit MMBroadbandModemMbimTelit; +typedef struct _MMBroadbandModemMbimTelitClass MMBroadbandModemMbimTelitClass; + +struct _MMBroadbandModemMbimTelit { + MMBroadbandModemMbim parent; +}; + +struct _MMBroadbandModemMbimTelitClass{ + MMBroadbandModemMbimClass parent; +}; + +GType mm_broadband_modem_mbim_telit_get_type (void); + +MMBroadbandModemMbimTelit *mm_broadband_modem_mbim_telit_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id, + guint16 subsystem_vendor_id); + +#endif /* MM_BROADBAND_MODEM_TELIT_H */ diff --git a/src/plugins/telit/mm-broadband-modem-telit.c b/src/plugins/telit/mm-broadband-modem-telit.c new file mode 100644 index 00000000..1683d38a --- /dev/null +++ b/src/plugins/telit/mm-broadband-modem-telit.c @@ -0,0 +1,1562 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-location.h" +#include "mm-broadband-modem-telit.h" +#include "mm-modem-helpers-telit.h" +#include "mm-telit-enums-types.h" +#include "mm-shared-telit.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void shared_telit_init (MMSharedTelit *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemTelit, mm_broadband_modem_telit, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_TELIT, shared_telit_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)); + +#define CSIM_UNLOCK_MAX_TIMEOUT 3 + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED +} FeatureSupport; + +struct _MMBroadbandModemTelitPrivate { + FeatureSupport csim_lock_support; + MMTelitQssStatus qss_status; + MMTelitCsimLockState csim_lock_state; + GTask *csim_lock_task; + guint csim_lock_timeout_id; + gboolean parse_qss; + MMModemLocationSource enabled_sources; +}; + +typedef struct { + MMModemLocationSource source; + guint gps_enable_step; +} LocationGatheringContext; + +/* + * AT$GPSNMUN + * enable: 0 NMEA stream disabled (default) + * 1 NMEA stream enabled in the form $GPSNMUN: <nmea sentence><CR> + * 2 NMEA stream enabled in the form <nmea sentence><CR> + * 3 dedicated NMEA stream + * GGA: 0 disable (default), 1 enable + * GLL: 0 disable (default), 1 enable + * GSA: 0 disable (default), 1 enable + * GSV: 0 disable (default), 1 enable + * RMC: 0 disable (default), 1 enable + * VTG: 0 disable (default), 1 enable + */ +static const gchar *gps_enable[] = { + "$GPSP=1", + "$GPSNMUN=2,1,1,1,1,1,1" +}; + +static gboolean +disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LocationGatheringContext *ctx; + MMPortSerialGps *gps_port; + GError *error = NULL; + + mm_base_modem_at_command_finish (self, res, &error); + ctx = g_task_get_task_data (task); + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + /* Even if we get an error here, we try to close the GPS port */ + gps_port = mm_base_modem_peek_port_gps (self); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemTelit *telit = MM_BROADBAND_MODEM_TELIT (self); + gboolean stop_gps = FALSE; + LocationGatheringContext *ctx; + GTask *task; + + ctx = g_new (LocationGatheringContext, 1); + ctx->source = source; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + /* Only stop GPS engine if no GPS-related sources enabled */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + telit->priv->enabled_sources &= ~source; + + if (!(telit->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + stop_gps = TRUE; + } + + if (stop_gps) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "$GPSP=0", + 3, + FALSE, + (GAsyncReadyCallback)gps_disabled_ready, + task); + return; + } + /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +gps_enabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LocationGatheringContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + if (!mm_base_modem_at_command_finish (self, res, &error)) { + g_prefix_error (&error, "couldn't power up GNSS controller: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + /* After Receiver was powered up we still have to enable unsolicited NMEA events */ + if (ctx->gps_enable_step < G_N_ELEMENTS (gps_enable)) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + gps_enable[ctx->gps_enable_step++], + 3, + FALSE, + (GAsyncReadyCallback)gps_enabled_ready, + task); + return; + } + + mm_obj_dbg (self, "GNSS controller is powered up"); + + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMPortSerialGps *gps_port; + + gps_port = mm_base_modem_peek_port_gps (self); + if (!gps_port || + !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't open raw GPS serial port"); + } else + g_task_return_boolean (task, TRUE); + } else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemTelit *self = MM_BROADBAND_MODEM_TELIT (_self); + LocationGatheringContext *ctx; + gboolean start_gps = FALSE; + GError *error = NULL; + + if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + /* Now our own enabling */ + ctx = g_task_get_task_data (task); + + /* NMEA, RAW and UNMANAGED are all enabled in the same way */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + /* Only start GPS engine if not done already */ + if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + start_gps = TRUE; + self->priv->enabled_sources |= ctx->source; + } + + if (start_gps && ctx->gps_enable_step < G_N_ELEMENTS (gps_enable)) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + gps_enable[ctx->gps_enable_step++], + 3, + FALSE, + (GAsyncReadyCallback)gps_enabled_ready, + task); + return; + } + /* For any other location (e.g. 3GPP), or if GPS already running just return */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LocationGatheringContext *ctx; + GTask *task; + + ctx = g_new (LocationGatheringContext, 1); + ctx->source = source; + ctx->gps_enable_step = 0; + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + /* Chain up parent's gathering enable */ + iface_modem_location_parent->enable_location_gathering ( + self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); +} + +static void +trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +static gboolean +enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +setup_ports (MMBroadbandModem *self) +{ + MMPortSerialGps *gps_data_port; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_telit_parent_class)->setup_ports (self); + + gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_data_port) { + /* It may happen that the modem was started with GPS already enabled, + * in this case GPSP AT command returns always error. Disable it for consistency + */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "$GPSP=0", 3, FALSE, FALSE, NULL); + + /* Add handler for the NMEA traces */ + mm_port_serial_gps_add_trace_handler (gps_data_port, + (MMPortSerialGpsTraceFn)trace_received, + self, + NULL); + } +} + +static MMModemLocationSource +location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +gpsp_test_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + MMModemLocationSource sources; + + sources = GPOINTER_TO_UINT (g_task_get_task_data (task)); + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) { + mm_obj_dbg (self, "GPS controller not supported: %s", error->message); + g_clear_error (&error); + } else if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) + sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED); + + g_task_return_int (task, sources); + g_object_unref (task); +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + + sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "$GPSP=?", + 3, + TRUE, + (GAsyncReadyCallback)gpsp_test_ready, + task); +} + +static void +location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_location_parent->load_capabilities ( + self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Setup SIM hot swap (Modem interface) */ + +typedef enum { + QSS_SETUP_STEP_FIRST, + QSS_SETUP_STEP_QUERY, + QSS_SETUP_STEP_ENABLE_PRIMARY_PORT, + QSS_SETUP_STEP_ENABLE_SECONDARY_PORT, + QSS_SETUP_STEP_LAST +} QssSetupStep; + +typedef struct { + QssSetupStep step; + MMPortSerialAt *primary; + MMPortSerialAt *secondary; + GError *primary_error; + GError *secondary_error; +} QssSetupContext; + +static void qss_setup_step (GTask *task); +static void pending_csim_unlock_complete (MMBroadbandModemTelit *self); + +static void +telit_qss_unsolicited_handler (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemTelit *self) +{ + MMTelitQssStatus cur_qss_status; + MMTelitQssStatus prev_qss_status; + + if (!mm_get_int_from_match_info (match_info, 1, (gint*)&cur_qss_status)) + return; + + prev_qss_status = self->priv->qss_status; + self->priv->qss_status = cur_qss_status; + + if (self->priv->csim_lock_state >= CSIM_LOCK_STATE_LOCK_REQUESTED) { + + if (prev_qss_status > QSS_STATUS_SIM_REMOVED && cur_qss_status == QSS_STATUS_SIM_REMOVED) { + mm_obj_dbg (self, "QSS handler: #QSS=0 after +CSIM=1: CSIM locked!"); + self->priv->csim_lock_state = CSIM_LOCK_STATE_LOCKED; + } + + if (prev_qss_status == QSS_STATUS_SIM_REMOVED && cur_qss_status != QSS_STATUS_SIM_REMOVED) { + mm_obj_dbg (self, "QSS handler: #QSS>=1 after +CSIM=0: CSIM unlocked!"); + self->priv->csim_lock_state = CSIM_LOCK_STATE_UNLOCKED; + + if (self->priv->csim_lock_timeout_id) { + g_source_remove (self->priv->csim_lock_timeout_id); + self->priv->csim_lock_timeout_id = 0; + } + + pending_csim_unlock_complete (self); + } + + return; + } + + if (cur_qss_status != prev_qss_status) + mm_obj_dbg (self, "QSS handler: status changed %s -> %s", + mm_telit_qss_status_get_string (prev_qss_status), + mm_telit_qss_status_get_string (cur_qss_status)); + + if (self->priv->parse_qss == FALSE) { + mm_obj_dbg (self, "QSS handler: message ignored"); + return; + } + + if ((prev_qss_status == QSS_STATUS_SIM_REMOVED && cur_qss_status != QSS_STATUS_SIM_REMOVED) || + (prev_qss_status > QSS_STATUS_SIM_REMOVED && cur_qss_status == QSS_STATUS_SIM_REMOVED)) { + mm_obj_msg (self, "QSS handler: SIM swap detected"); + mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self)); + } +} + +static void +qss_setup_context_free (QssSetupContext *ctx) +{ + g_clear_object (&(ctx->primary)); + g_clear_object (&(ctx->secondary)); + g_clear_error (&(ctx->primary_error)); + g_clear_error (&(ctx->secondary_error)); + g_slice_free (QssSetupContext, ctx); +} + +static gboolean +modem_setup_sim_hot_swap_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +telit_qss_enable_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + QssSetupContext *ctx; + MMPortSerialAt *port; + GError **error; + g_autoptr(GRegex) pattern = NULL; + + ctx = g_task_get_task_data (task); + + if (ctx->step == QSS_SETUP_STEP_ENABLE_PRIMARY_PORT) { + port = ctx->primary; + error = &ctx->primary_error; + } else if (ctx->step == QSS_SETUP_STEP_ENABLE_SECONDARY_PORT) { + port = ctx->secondary; + error = &ctx->secondary_error; + } else + g_assert_not_reached (); + + if (!mm_base_modem_at_command_full_finish (self, res, error)) { + mm_obj_warn (self, "QSS: error enabling unsolicited on port %s: %s", mm_port_get_device (MM_PORT (port)), (*error)->message); + goto next_step; + } + + pattern = g_regex_new ("#QSS:\\s*([0-3])\\r\\n", G_REGEX_RAW, 0, NULL); + g_assert (pattern); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + pattern, + (MMPortSerialAtUnsolicitedMsgFn)telit_qss_unsolicited_handler, + self, + NULL); + +next_step: + ctx->step++; + qss_setup_step (task); +} + +static void +telit_qss_query_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemTelit *self; + GError *error = NULL; + const gchar *response; + MMTelitQssStatus qss_status; + QssSetupContext *ctx; + + self = MM_BROADBAND_MODEM_TELIT (_self); + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (error) { + mm_obj_warn (self, "could not get \"#QSS?\" reply: %s", error->message); + g_error_free (error); + goto next_step; + } + + qss_status = mm_telit_parse_qss_query (response, &error); + if (error) { + mm_obj_warn (self, "QSS query parse error: %s", error->message); + g_error_free (error); + goto next_step; + } + + mm_obj_dbg (self, "QSS: current status is '%s'", mm_telit_qss_status_get_string (qss_status)); + self->priv->qss_status = qss_status; + +next_step: + ctx->step++; + qss_setup_step (task); +} + +static void +telit_qss_support_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + QssSetupContext *ctx; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "#QSS command unsupported: '%s'", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx->step++; + qss_setup_step (task); +} + +static void +qss_setup_step (GTask *task) +{ + QssSetupContext *ctx; + MMBroadbandModemTelit *self; + + self = MM_BROADBAND_MODEM_TELIT (g_task_get_source_object (task)); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case QSS_SETUP_STEP_FIRST: + mm_base_modem_at_command (MM_BASE_MODEM (self), + "#QSS=?", + 3, + TRUE, + (GAsyncReadyCallback) telit_qss_support_ready, + task); + return; + case QSS_SETUP_STEP_QUERY: + mm_base_modem_at_command (MM_BASE_MODEM (self), + "#QSS?", + 3, + FALSE, + (GAsyncReadyCallback) telit_qss_query_ready, + task); + return; + case QSS_SETUP_STEP_ENABLE_PRIMARY_PORT: + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + ctx->primary, + "#QSS=1", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback) telit_qss_enable_ready, + task); + return; + case QSS_SETUP_STEP_ENABLE_SECONDARY_PORT: + if (ctx->secondary) { + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + ctx->secondary, + "#QSS=1", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback) telit_qss_enable_ready, + task); + return; + } + ctx->step++; + /* fall through */ + case QSS_SETUP_STEP_LAST: + /* If all enabling actions failed (either both, or only primary if + * there is no secondary), then we return an error */ + if (ctx->primary_error && (ctx->secondary_error || !ctx->secondary)) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "QSS: couldn't enable unsolicited"); + } else { + g_autoptr(GError) error = NULL; + + if (!mm_broadband_modem_sim_hot_swap_ports_context_init (MM_BROADBAND_MODEM (self), &error)) + mm_obj_warn (self, "failed to initialize SIM hot swap ports context: %s", error->message); + + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); + break; + + default: + g_assert_not_reached (); + } +} + +static void +modem_setup_sim_hot_swap (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + QssSetupContext *ctx; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_slice_new0 (QssSetupContext); + ctx->step = QSS_SETUP_STEP_FIRST; + ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); + ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); + + g_task_set_task_data (task, ctx, (GDestroyNotify) qss_setup_context_free); + qss_setup_step (task); +} + +/*****************************************************************************/ +/* SIM hot swap cleanup (Modem interface) */ + +static void +modem_cleanup_sim_hot_swap (MMIfaceModem *self) +{ + mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self)); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) + * + * NOTE: the logic must make sure that LOAD_UNLOCK_RETRIES_STEP_UNLOCK is always + * run if LOAD_UNLOCK_RETRIES_STEP_LOCK has been run. Currently, the logic just + * runs all intermediate steps ignoring errors (i.e. not completing the + * operation if something fails), so the LOAD_UNLOCK_RETRIES_STEP_UNLOCK is + * always run. + */ + +#define CSIM_LOCK_STR "+CSIM=1" +#define CSIM_UNLOCK_STR "+CSIM=0" +#define CSIM_QUERY_TIMEOUT 3 + +typedef enum { + LOAD_UNLOCK_RETRIES_STEP_FIRST, + LOAD_UNLOCK_RETRIES_STEP_LOCK, + LOAD_UNLOCK_RETRIES_STEP_PARENT, + LOAD_UNLOCK_RETRIES_STEP_UNLOCK, + LOAD_UNLOCK_RETRIES_STEP_LAST +} LoadUnlockRetriesStep; + +typedef struct { + MMUnlockRetries *retries; + LoadUnlockRetriesStep step; +} LoadUnlockRetriesContext; + +static void load_unlock_retries_step (GTask *task); + +static void +load_unlock_retries_context_free (LoadUnlockRetriesContext *ctx) +{ + if (ctx->retries) + g_object_unref (ctx->retries); + g_slice_free (LoadUnlockRetriesContext, ctx); +} + +static MMUnlockRetries * +modem_load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return (MMUnlockRetries *) g_task_propagate_pointer (G_TASK (res), error); +} + +static void +csim_unlock_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + MMBroadbandModemTelit *self; + LoadUnlockRetriesContext *ctx; + + self = MM_BROADBAND_MODEM_TELIT (_self); + ctx = g_task_get_task_data (task); + + /* Ignore errors */ + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) { + if (g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED)) { + self->priv->csim_lock_support = FEATURE_NOT_SUPPORTED; + } + mm_obj_warn (self, "couldn't unlock SIM card: %s", error->message); + g_error_free (error); + } + + if (self->priv->csim_lock_support != FEATURE_NOT_SUPPORTED) + self->priv->csim_lock_support = FEATURE_SUPPORTED; + + ctx->step++; + load_unlock_retries_step (task); +} + +static void +parent_load_unlock_retries_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + LoadUnlockRetriesContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!(ctx->retries = iface_modem_parent->load_unlock_retries_finish (self, res, &error))) { + mm_obj_warn (self, "couldn't load unlock retries with generic logic: %s", error->message); + g_error_free (error); + } + + ctx->step++; + load_unlock_retries_step (task); +} + +static void +csim_lock_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + MMBroadbandModemTelit *self; + LoadUnlockRetriesContext *ctx; + + self = MM_BROADBAND_MODEM_TELIT (_self); + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) { + if (g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED) || + g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) { + self->priv->csim_lock_support = FEATURE_NOT_SUPPORTED; + mm_obj_warn (self, "couldn't lock SIM card: %s; continuing without CSIM lock", error->message); + g_error_free (error); + } else { + g_prefix_error (&error, "Couldn't lock SIM card: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + } else { + self->priv->csim_lock_state = CSIM_LOCK_STATE_LOCK_REQUESTED; + } + + if (self->priv->csim_lock_support != FEATURE_NOT_SUPPORTED) { + self->priv->csim_lock_support = FEATURE_SUPPORTED; + } + + ctx->step++; + load_unlock_retries_step (task); +} + +static void +handle_csim_locking (GTask *task, + gboolean is_lock) +{ + MMBroadbandModemTelit *self; + LoadUnlockRetriesContext *ctx; + + self = MM_BROADBAND_MODEM_TELIT (g_task_get_source_object (task)); + ctx = g_task_get_task_data (task); + + switch (self->priv->csim_lock_support) { + case FEATURE_SUPPORT_UNKNOWN: + case FEATURE_SUPPORTED: + if (is_lock) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + CSIM_LOCK_STR, + CSIM_QUERY_TIMEOUT, + FALSE, + (GAsyncReadyCallback) csim_lock_ready, + task); + } else { + mm_base_modem_at_command (MM_BASE_MODEM (self), + CSIM_UNLOCK_STR, + CSIM_QUERY_TIMEOUT, + FALSE, + (GAsyncReadyCallback) csim_unlock_ready, + task); + } + break; + case FEATURE_NOT_SUPPORTED: + mm_obj_dbg (self, "CSIM lock not supported by this modem; skipping %s command", + is_lock ? "lock" : "unlock"); + ctx->step++; + load_unlock_retries_step (task); + break; + default: + g_assert_not_reached (); + break; + } +} + +static void +pending_csim_unlock_complete (MMBroadbandModemTelit *self) +{ + LoadUnlockRetriesContext *ctx; + + ctx = g_task_get_task_data (self->priv->csim_lock_task); + + if (!ctx->retries) { + g_task_return_new_error (self->priv->csim_lock_task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not get any of the SIM unlock retries values"); + } else { + g_task_return_pointer (self->priv->csim_lock_task, g_object_ref (ctx->retries), g_object_unref); + } + + g_clear_object (&self->priv->csim_lock_task); +} + +static gboolean +csim_unlock_periodic_check (MMBroadbandModemTelit *self) +{ + if (self->priv->csim_lock_state != CSIM_LOCK_STATE_UNLOCKED) + mm_obj_warn (self, "CSIM is still locked after %d seconds; trying to continue anyway", CSIM_UNLOCK_MAX_TIMEOUT); + + self->priv->csim_lock_timeout_id = 0; + pending_csim_unlock_complete (self); + g_object_unref (self); + + return G_SOURCE_REMOVE; +} + +static void +load_unlock_retries_step (GTask *task) +{ + MMBroadbandModemTelit *self; + LoadUnlockRetriesContext *ctx; + + self = MM_BROADBAND_MODEM_TELIT (g_task_get_source_object (task)); + ctx = g_task_get_task_data (task); + switch (ctx->step) { + case LOAD_UNLOCK_RETRIES_STEP_FIRST: + ctx->step++; + /* fall through */ + case LOAD_UNLOCK_RETRIES_STEP_LOCK: + handle_csim_locking (task, TRUE); + break; + case LOAD_UNLOCK_RETRIES_STEP_PARENT: + iface_modem_parent->load_unlock_retries ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_unlock_retries_ready, + task); + break; + case LOAD_UNLOCK_RETRIES_STEP_UNLOCK: + handle_csim_locking (task, FALSE); + break; + case LOAD_UNLOCK_RETRIES_STEP_LAST: + self->priv->csim_lock_task = task; + if (self->priv->csim_lock_state == CSIM_LOCK_STATE_LOCKED) { + mm_obj_dbg (self, "CSIM is locked, waiting for #QSS=1"); + self->priv->csim_lock_timeout_id = g_timeout_add_seconds (CSIM_UNLOCK_MAX_TIMEOUT, + (GSourceFunc) csim_unlock_periodic_check, + g_object_ref(self)); + } else { + self->priv->csim_lock_state = CSIM_LOCK_STATE_UNLOCKED; + pending_csim_unlock_complete (self); + } + break; + default: + break; + } +} + +static void +modem_load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LoadUnlockRetriesContext *ctx; + + g_assert (iface_modem_parent->load_unlock_retries); + g_assert (iface_modem_parent->load_unlock_retries_finish); + + ctx = g_slice_new0 (LoadUnlockRetriesContext); + ctx->step = LOAD_UNLOCK_RETRIES_STEP_FIRST; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)load_unlock_retries_context_free); + + load_unlock_retries_step (task); +} + +/*****************************************************************************/ +/* Modem after power up (Modem interface) */ + +static gboolean +modem_after_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +modem_after_power_up (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMBroadbandModemTelit *modem = MM_BROADBAND_MODEM_TELIT (self); + + task = g_task_new (self, NULL, callback, user_data); + + mm_obj_dbg (self, "stop ignoring #QSS"); + modem->priv->parse_qss = TRUE; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +telit_modem_power_down_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "sgnore #QSS unsolicited during power down/low"); + MM_BROADBAND_MODEM_TELIT (self)->priv->parse_qss = FALSE; + } + + if (error) { + mm_obj_warn (self, "failed modem power down: %s", error->message); + g_clear_error (&error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=4", + 20, + FALSE, + (GAsyncReadyCallback) telit_modem_power_down_ready, + task); +} + +/*****************************************************************************/ +/* Reset (Modem interface) */ + +static gboolean +modem_reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT#REBOOT", + 8, + FALSE, + callback, + user_data); +} +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + GVariant *result; + + result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error); + if (!result) { + if (error) + g_assert (*error); + return FALSE; + } + + *access_technologies = (MMModemAccessTechnology) g_variant_get_uint32 (result); + *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; + return TRUE; +} + +static MMBaseModemAtResponseProcessorResult +response_processor_cops_ignore_at_errors (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GRegex) r = NULL; + guint actval = 0; + guint mode = 0; + guint vid; + guint pid; + + *result = NULL; + *result_error = NULL; + + if (error) { + /* 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; + } + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + } + + vid = mm_base_modem_get_vendor_id (self); + pid = mm_base_modem_get_product_id (self); + + if (!(vid == 0x1bc7 && (pid == 0x110a || pid == 0x110b))) { + /* AcT for non-LPWA modems would be checked by other command */ + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + } + + r = g_regex_new ("\\+COPS:\\s*(\\d+),(\\d+),([^,]*)(?:,(\\d+))?(?:\\r\\n)?", + 0, + 0, + NULL); + g_assert (r != NULL); + + if (!g_regex_match (r, response, 0, &match_info)) { + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Can't match +COPS? response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + + if (!mm_get_uint_from_match_info (match_info, 1, &mode)) { + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse mode in +COPS? response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + + if (mode == 2) { + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Modem deregistered from the network: aborting AcT query"); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + + if (!mm_get_uint_from_match_info (match_info, 4, &actval)) { + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse act in +COPS? response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + + switch (actval) { + case 0: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_GSM); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 8: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_LTE_CAT_M); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 9: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_LTE_NB_IOT); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + default: + break; + } + + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to map act in +COPS? response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; +} + +static MMBaseModemAtResponseProcessorResult +response_processor_psnt_ignore_at_errors (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + const gchar *psnt; + const gchar *mode; + + *result = NULL; + *result_error = NULL; + + if (error) { + /* 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; + } + + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + } + + psnt = mm_strip_tag (response, "#PSNT:"); + mode = strchr (psnt, ','); + if (mode) { + switch (atoi (++mode)) { + case 0: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_GPRS); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 1: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_EDGE); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 2: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_UMTS); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 3: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_HSDPA); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 4: + if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_LTE); + else + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 5: + if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) { + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + } + /* Fall-through since #PSNT: 5 is not supported in other than lte modems */ + default: + break; + } + } + + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse #PSNT response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; +} + +static MMBaseModemAtResponseProcessorResult +response_processor_service_ignore_at_errors (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + const gchar *service; + + *result = NULL; + *result_error = NULL; + + if (error) { + /* 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; + } + + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + } + + service = mm_strip_tag (response, "+SERVICE:"); + if (service) { + switch (atoi (service)) { + case 1: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_1XRTT); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 2: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_EVDO0); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 3: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_EVDOA); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + default: + break; + } + } + + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse +SERVICE response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; +} + +static const MMBaseModemAtCommand access_tech_commands[] = { + { "+COPS?", 3, FALSE, response_processor_cops_ignore_at_errors }, + { "#PSNT?", 3, FALSE, response_processor_psnt_ignore_at_errors }, + { "+SERVICE?", 3, FALSE, response_processor_service_ignore_at_errors }, + { NULL } +}; + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_sequence ( + MM_BASE_MODEM (self), + access_tech_commands, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + callback, + user_data); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return (GArray *) g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMSharedTelit *shared = MM_SHARED_TELIT (self); + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* CDMA-only modems don't support changing modes, default to parent's */ + if (!mm_iface_modem_is_3gpp (self)) { + g_task_return_pointer (task, all, (GDestroyNotify) g_array_unref); + g_object_unref (task); + return; + } + + /* Filter out those unsupported modes */ + combinations = mm_telit_build_modes_list(); + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + mm_shared_telit_store_supported_modes (shared, filtered); + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Enabling unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cind_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't enable custom +CIND settings: %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't enable parent 3GPP unsolicited events: %s", error->message); + g_error_free (error); + } + + /* Our own enable now */ + mm_base_modem_at_command_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)), + /* Enable +CIEV only for: signal, service, roam */ + "AT+CIND=0,1,1,0,0,0,1,0,0", + 5, + FALSE, + FALSE, + NULL, /* cancellable */ + (GAsyncReadyCallback)cind_set_ready, + task); +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's enable */ + iface_modem_3gpp_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ + +MMBroadbandModemTelit * +mm_broadband_modem_telit_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_TELIT, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports AT only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_telit_init (MMBroadbandModemTelit *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_TELIT, + MMBroadbandModemTelitPrivate); + + self->priv->csim_lock_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->csim_lock_state = CSIM_LOCK_STATE_UNKNOWN; + self->priv->qss_status = QSS_STATUS_UNKNOWN; + self->priv->parse_qss = TRUE; +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->set_current_bands = mm_shared_telit_modem_set_current_bands; + iface->set_current_bands_finish = mm_shared_telit_modem_set_current_bands_finish; + iface->load_current_bands = mm_shared_telit_modem_load_current_bands; + iface->load_current_bands_finish = mm_shared_telit_modem_load_current_bands_finish; + iface->load_revision = mm_shared_telit_modem_load_revision; + iface->load_revision_finish = mm_shared_telit_modem_load_revision_finish; + iface->load_supported_bands = mm_shared_telit_modem_load_supported_bands; + iface->load_supported_bands_finish = mm_shared_telit_modem_load_supported_bands_finish; + iface->load_unlock_retries_finish = modem_load_unlock_retries_finish; + iface->load_unlock_retries = modem_load_unlock_retries; + iface->reset = modem_reset; + iface->reset_finish = modem_reset_finish; + iface->modem_after_power_up = modem_after_power_up; + iface->modem_after_power_up_finish = modem_after_power_up_finish; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = mm_shared_telit_load_current_modes; + iface->load_current_modes_finish = mm_shared_telit_load_current_modes_finish; + iface->set_current_modes = mm_shared_telit_set_current_modes; + iface->set_current_modes_finish = mm_shared_telit_set_current_modes_finish; + iface->setup_sim_hot_swap = modem_setup_sim_hot_swap; + iface->setup_sim_hot_swap_finish = modem_setup_sim_hot_swap_finish; + iface->cleanup_sim_hot_swap = modem_cleanup_sim_hot_swap; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish; +} + +static void +shared_telit_init (MMSharedTelit *iface) +{ +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = location_load_capabilities; + iface->load_capabilities_finish = location_load_capabilities_finish; + iface->enable_location_gathering = enable_location_gathering; + iface->enable_location_gathering_finish = enable_location_gathering_finish; + iface->disable_location_gathering = disable_location_gathering; + iface->disable_location_gathering_finish = disable_location_gathering_finish; +} + +static void +mm_broadband_modem_telit_class_init (MMBroadbandModemTelitClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemTelitPrivate)); + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/telit/mm-broadband-modem-telit.h b/src/plugins/telit/mm-broadband-modem-telit.h new file mode 100644 index 00000000..f68465e7 --- /dev/null +++ b/src/plugins/telit/mm-broadband-modem-telit.h @@ -0,0 +1,51 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2013 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_TELIT_H +#define MM_BROADBAND_MODEM_TELIT_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_TELIT (mm_broadband_modem_telit_get_type ()) +#define MM_BROADBAND_MODEM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_TELIT, MMBroadbandModemTelit)) +#define MM_BROADBAND_MODEM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_TELIT, MMBroadbandModemTelitClass)) +#define MM_IS_BROADBAND_MODEM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_TELIT)) +#define MM_IS_BROADBAND_MODEM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_TELIT)) +#define MM_BROADBAND_MODEM_TELIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_TELIT, MMBroadbandModemTelitClass)) + +typedef struct _MMBroadbandModemTelit MMBroadbandModemTelit; +typedef struct _MMBroadbandModemTelitClass MMBroadbandModemTelitClass; +typedef struct _MMBroadbandModemTelitPrivate MMBroadbandModemTelitPrivate; + +struct _MMBroadbandModemTelit { + MMBroadbandModem parent; + MMBroadbandModemTelitPrivate *priv; +}; + +struct _MMBroadbandModemTelitClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_telit_get_type (void); + +MMBroadbandModemTelit *mm_broadband_modem_telit_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_TELIT_H */ diff --git a/src/plugins/telit/mm-common-telit.c b/src/plugins/telit/mm-common-telit.c new file mode 100644 index 00000000..911c605b --- /dev/null +++ b/src/plugins/telit/mm-common-telit.c @@ -0,0 +1,373 @@ +/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> + +#include "mm-common-telit.h" +#include "mm-log-object.h" +#include "mm-serial-parsers.h" + +/*****************************************************************************/ + +#define TAG_GETPORTCFG_SUPPORTED "getportcfg-supported" + +#define TAG_TELIT_MODEM_PORT "ID_MM_TELIT_PORT_TYPE_MODEM" +#define TAG_TELIT_AUX_PORT "ID_MM_TELIT_PORT_TYPE_AUX" +#define TAG_TELIT_NMEA_PORT "ID_MM_TELIT_PORT_TYPE_NMEA" + +#define TELIT_GE910_FAMILY_PID 0x0022 + +/* The following number of retries of the port responsiveness + * check allows having up to 30 seconds of wait, that should + * be fine for most of the modems */ +#define TELIT_PORT_CHECK_RETRIES 6 + +gboolean +telit_grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + MMKernelDevice *port; + MMDevice *device; + MMPortType ptype; + MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE; + const gchar *subsys; + + port = mm_port_probe_peek_port (probe); + ptype = mm_port_probe_get_port_type (probe); + device = mm_port_probe_peek_device (probe); + subsys = mm_port_probe_get_port_subsys (probe); + + /* Just skip custom port identification for subsys different than tty */ + if (!g_str_equal (subsys, "tty")) + goto out; + + /* AT#PORTCFG (if supported) can be used for identifying the port layout */ + if (g_object_get_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED) != NULL) { + guint usbif; + + usbif = (guint) mm_kernel_device_get_interface_number (port); + if (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT))) { + mm_obj_dbg (self, "AT port '%s/%s' flagged as primary", + mm_port_probe_get_port_subsys (probe), + mm_port_probe_get_port_name (probe)); + pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY; + } else if (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_AUX_PORT))) { + mm_obj_dbg (self, "AT port '%s/%s' flagged as secondary", + mm_port_probe_get_port_subsys (probe), + mm_port_probe_get_port_name (probe)); + pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY; + } else if (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT))) { + mm_obj_dbg (self, "port '%s/%s' flagged as NMEA", + mm_port_probe_get_port_subsys (probe), + mm_port_probe_get_port_name (probe)); + ptype = MM_PORT_TYPE_GPS; + } else + ptype = MM_PORT_TYPE_IGNORED; + } + +out: + return mm_base_modem_grab_port (modem, + port, + ptype, + pflags, + error); +} + +/*****************************************************************************/ +/* Custom init */ + +typedef struct { + MMPortSerialAt *port; + gboolean getportcfg_done; + guint getportcfg_retries; + guint port_responsive_retries; +} TelitCustomInitContext; + +gboolean +telit_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void telit_custom_init_step (GTask *task); + +static gboolean +cache_port_mode (MMPortProbe *probe, + MMDevice *device, + const gchar *reply) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW; + GError *error = NULL; + gboolean ret = FALSE; + guint portcfg_current; + + /* #PORTCFG: <requested>,<active> */ + r = g_regex_new ("#PORTCFG:\\s*(\\d+),(\\d+)", flags, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &error)) + goto out; + + if (!mm_get_uint_from_match_info (match_info, 2, &portcfg_current)) { + mm_obj_dbg (probe, "unrecognized #PORTCFG <active> value"); + goto out; + } + + /* Reference for port configurations: + * HE910/UE910/UL865 Families Ports Arrangements User Guide + * GE910 Family Ports Arrangements User Guide + */ + switch (portcfg_current) { + case 0: + case 1: + case 4: + case 5: + case 7: + case 9: + case 10: + case 11: + g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00)); + if (mm_device_get_product (device) == TELIT_GE910_FAMILY_PID) + g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x02)); + else + g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x06)); + break; + case 2: + case 3: + case 6: + g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00)); + break; + case 8: + case 12: + g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00)); + if (mm_device_get_product (device) == TELIT_GE910_FAMILY_PID) { + g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x02)); + g_object_set_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT, GUINT_TO_POINTER (0x04)); + } else { + g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x06)); + g_object_set_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT, GUINT_TO_POINTER (0x0a)); + } + break; + default: + /* portcfg value not supported */ + goto out; + } + ret = TRUE; + +out: + if (error) { + mm_obj_dbg (probe, "error while matching #PORTCFG: %s", error->message); + g_error_free (error); + } + return ret; +} + +static void +getportcfg_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + MMPortProbe *probe; + TelitCustomInitContext *ctx; + + ctx = g_task_get_task_data (task); + probe = g_task_get_source_object (task); + + response = mm_port_serial_at_command_finish (port, res, &error); + if (error) { + mm_obj_dbg (probe, "couldn't get telit port mode: '%s'", error->message); + + /* If ERROR or COMMAND NOT SUPPORT occur then do not retry the + * command. + */ + if (g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) + ctx->getportcfg_done = TRUE; + } else { + MMDevice *device; + + device = mm_port_probe_peek_device (probe); + + /* Results are cached in the parent device object */ + if (g_object_get_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED) == NULL) { + mm_obj_dbg (probe, "retrieving telit port mode layout"); + if (cache_port_mode (probe, device, response)) { + g_object_set_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED, GUINT_TO_POINTER (TRUE)); + ctx->getportcfg_done = TRUE; + } + } + + /* Port answered to #PORTCFG, so mark it as being AT already */ + mm_port_probe_set_result_at (probe, TRUE); + } + + if (error) + g_error_free (error); + + telit_custom_init_step (task); +} + +static void +telit_custom_init_context_free (TelitCustomInitContext *ctx) +{ + g_object_unref (ctx->port); + g_slice_free (TelitCustomInitContext, ctx); +} + +static void +telit_custom_init_step (GTask *task) +{ + MMKernelDevice *port; + MMPortProbe *probe; + TelitCustomInitContext *ctx; + + ctx = g_task_get_task_data (task); + probe = g_task_get_source_object (task); + + /* If cancelled, end */ + if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) { + mm_obj_dbg (probe, "no need to keep on running custom init"); + goto out; + } + + /* Try to get a port configuration from the modem: usb interface 00 + * is always linked to an AT port + */ + port = mm_port_probe_peek_port (probe); + if (!ctx->getportcfg_done && mm_kernel_device_get_interface_number (port) == 0) { + if (ctx->getportcfg_retries == 0) + goto out; + ctx->getportcfg_retries--; + + mm_port_serial_at_command ( + ctx->port, + "AT#PORTCFG?", + 2, + FALSE, /* raw */ + FALSE, /* allow_cached */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)getportcfg_ready, + task); + return; + } + +out: + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void at_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task); + +static void +wait_for_ready (GTask *task) +{ + TelitCustomInitContext *ctx; + + ctx = g_task_get_task_data (task); + + if (ctx->port_responsive_retries == 0) { + telit_custom_init_step (task); + return; + } + ctx->port_responsive_retries--; + + mm_port_serial_at_command ( + ctx->port, + "AT", + 5, + FALSE, /* raw */ + FALSE, /* allow_cached */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)at_ready, + task); +} + +static void +at_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + MMPortProbe *probe; + g_autoptr(GError) error = NULL; + + probe = g_task_get_source_object (task); + + mm_port_serial_at_command_finish (port, res, &error); + if (error) { + /* On a timeout or send error, wait */ + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT) || + g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED)) { + wait_for_ready (task); + return; + } + /* On an unknown error, make it fatal */ + if (!mm_serial_parser_v1_is_known_error (error)) { + mm_obj_warn (probe, "custom port initialization logic failed: %s", error->message); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + } + + /* When successful mark the port as AT and continue checking #PORTCFG */ + mm_obj_dbg (probe, "port is AT"); + mm_port_probe_set_result_at (probe, TRUE); + telit_custom_init_step (task); +} + +void +telit_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TelitCustomInitContext *ctx; + GTask *task; + gboolean wait_needed; + + ctx = g_slice_new (TelitCustomInitContext); + ctx->port = g_object_ref (port); + ctx->getportcfg_done = FALSE; + ctx->getportcfg_retries = 3; + ctx->port_responsive_retries = TELIT_PORT_CHECK_RETRIES; + task = g_task_new (probe, cancellable, callback, user_data); + g_task_set_check_cancellable (task, FALSE); + g_task_set_task_data (task, ctx, (GDestroyNotify)telit_custom_init_context_free); + + /* Some Telit modems require an initial delay for the ports to be responsive + * If no explicit tag is present, the modem does not need this step + * and can directly look for #PORTCFG support */ + wait_needed = mm_kernel_device_get_global_property_as_boolean (mm_port_probe_peek_port (probe), + "ID_MM_TELIT_PORT_DELAY"); + if (wait_needed) { + mm_obj_dbg (probe, "Start polling for port responsiveness"); + wait_for_ready (task); + return; + } + + telit_custom_init_step (task); +} diff --git a/src/plugins/telit/mm-common-telit.h b/src/plugins/telit/mm-common-telit.h new file mode 100644 index 00000000..0c5dbe7a --- /dev/null +++ b/src/plugins/telit/mm-common-telit.h @@ -0,0 +1,40 @@ +/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_COMMON_TELIT_H +#define MM_COMMON_TELIT_H + +#include "glib.h" +#include "mm-plugin.h" + +gboolean +telit_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error); + +void +telit_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean +telit_grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error); + +#endif /* MM_COMMON_TELIT_H */ diff --git a/src/plugins/telit/mm-modem-helpers-telit.c b/src/plugins/telit/mm-modem-helpers-telit.c new file mode 100644 index 00000000..c0df8093 --- /dev/null +++ b/src/plugins/telit/mm-modem-helpers-telit.c @@ -0,0 +1,967 @@ +/* -*- 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) 2015-2019 Telit. + * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MMCLI +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-telit.h" + +/*****************************************************************************/ +/* AT#BND 2G values */ + +#define MM_MODEM_BAND_TELIT_2G_FIRST MM_MODEM_BAND_EGSM +#define MM_MODEM_BAND_TELIT_2G_LAST MM_MODEM_BAND_G850 + +#define B2G_FLAG(band) (1 << (band - MM_MODEM_BAND_TELIT_2G_FIRST)) + +/* Index of the array is the telit 2G band value [0-5] + * The bitmask value here is built from the 2G MMModemBand values right away. */ +static const guint32 telit_2g_to_mm_band_mask[] = { + [0] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS), + [1] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_PCS), + [2] = B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_G850), + [3] = B2G_FLAG (MM_MODEM_BAND_PCS) + B2G_FLAG (MM_MODEM_BAND_G850), + [4] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_PCS), + [5] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_PCS) + B2G_FLAG (MM_MODEM_BAND_G850), +}; + +/*****************************************************************************/ +/* AT#BND 3G values */ + +/* NOTE: UTRAN_1 to UTRAN_9 enum values are NOT IN ORDER! + * E.g. numerically UTRAN_7 is after UTRAN_9... + * + * This array allows us to iterate them in a way which is a bit + * more friendly. + */ +static const guint band_utran_index[] = { + [MM_MODEM_BAND_UTRAN_1] = 1, + [MM_MODEM_BAND_UTRAN_2] = 2, + [MM_MODEM_BAND_UTRAN_3] = 3, + [MM_MODEM_BAND_UTRAN_4] = 4, + [MM_MODEM_BAND_UTRAN_5] = 5, + [MM_MODEM_BAND_UTRAN_6] = 6, + [MM_MODEM_BAND_UTRAN_7] = 7, + [MM_MODEM_BAND_UTRAN_8] = 8, + [MM_MODEM_BAND_UTRAN_9] = 9, + [MM_MODEM_BAND_UTRAN_10] = 10, + [MM_MODEM_BAND_UTRAN_11] = 11, + [MM_MODEM_BAND_UTRAN_12] = 12, + [MM_MODEM_BAND_UTRAN_13] = 13, + [MM_MODEM_BAND_UTRAN_14] = 14, + [MM_MODEM_BAND_UTRAN_19] = 19, + [MM_MODEM_BAND_UTRAN_20] = 20, + [MM_MODEM_BAND_UTRAN_21] = 21, + [MM_MODEM_BAND_UTRAN_22] = 22, + [MM_MODEM_BAND_UTRAN_25] = 25, + [MM_MODEM_BAND_UTRAN_26] = 26, + [MM_MODEM_BAND_UTRAN_32] = 32, +}; + +#define MM_MODEM_BAND_TELIT_3G_FIRST MM_MODEM_BAND_UTRAN_1 +#define MM_MODEM_BAND_TELIT_3G_LAST MM_MODEM_BAND_UTRAN_19 + +#define B3G_NUM(band) band_utran_index[band] +#define B3G_FLAG(band) ((B3G_NUM (band) > 0) ? (1LL << (B3G_NUM (band) - B3G_NUM (MM_MODEM_BAND_TELIT_3G_FIRST))) : 0) + +/* Index of the arrays is the telit 3G band value. + * The bitmask value here is built from the 3G MMModemBand values right away. + * + * We have 2 different sets of bands that are different for values >=12, because + * the LM940/960 models support a different set of 3G bands. + */ + +#define TELIT_3G_TO_MM_BAND_MASK_DEFAULT_N_ELEMENTS 27 +static guint64 telit_3g_to_mm_band_mask_default[TELIT_3G_TO_MM_BAND_MASK_DEFAULT_N_ELEMENTS]; + +#define TELIT_3G_TO_MM_BAND_MASK_ALTERNATE_N_ELEMENTS 20 +static guint64 telit_3g_to_mm_band_mask_alternate[TELIT_3G_TO_MM_BAND_MASK_ALTERNATE_N_ELEMENTS]; + +static void +initialize_telit_3g_to_mm_band_masks (void) +{ + static gboolean initialized = FALSE; + + /* We need to initialize the arrays in runtime because gcc < 8 doesn't like initializing + * with operations that are using the band_utran_index constant array elements */ + + if (initialized) + return; + + telit_3g_to_mm_band_mask_default[0] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1); + telit_3g_to_mm_band_mask_default[1] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2); + telit_3g_to_mm_band_mask_default[2] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[3] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[4] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[5] = B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[6] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[7] = B3G_FLAG (MM_MODEM_BAND_UTRAN_4); + telit_3g_to_mm_band_mask_default[8] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[9] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[10] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[11] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[12] = B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[13] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3); + telit_3g_to_mm_band_mask_default[14] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[15] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[16] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[17] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[18] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[19] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[20] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[21] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[22] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[23] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3); + telit_3g_to_mm_band_mask_default[24] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[25] = B3G_FLAG (MM_MODEM_BAND_UTRAN_19); + telit_3g_to_mm_band_mask_default[26] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_19); + + /* Initialize alternate 3G band set */ + telit_3g_to_mm_band_mask_alternate[0] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1); + telit_3g_to_mm_band_mask_alternate[1] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2); + telit_3g_to_mm_band_mask_alternate[2] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[3] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[4] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[5] = B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[6] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[7] = B3G_FLAG (MM_MODEM_BAND_UTRAN_4); + telit_3g_to_mm_band_mask_alternate[8] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[9] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[10] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[11] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[12] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[13] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3); + telit_3g_to_mm_band_mask_alternate[14] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[15] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[16] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[17] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[18] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_9) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_19); + telit_3g_to_mm_band_mask_alternate[19] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_9) + B3G_FLAG (MM_MODEM_BAND_UTRAN_19); +} + +/*****************************************************************************/ +/* AT#BND 4G values + * + * The Telit-specific value for 4G bands is a bitmask of the band values, given + * in hexadecimal or decimal format. + */ + +#define MM_MODEM_BAND_TELIT_4G_FIRST MM_MODEM_BAND_EUTRAN_1 +#define MM_MODEM_BAND_TELIT_4G_LAST MM_MODEM_BAND_EUTRAN_64 + +#define B4G_FLAG(band) (((guint64) 1) << (band - MM_MODEM_BAND_TELIT_4G_FIRST)) + +#define MM_MODEM_BAND_TELIT_EXT_4G_FIRST MM_MODEM_BAND_EUTRAN_65 +#define MM_MODEM_BAND_TELIT_EXT_4G_LAST MM_MODEM_BAND_EUTRAN_85 + +#define B4G_FLAG_EXT(band) (((guint64) 1) << (band - MM_MODEM_BAND_TELIT_EXT_4G_FIRST)) + +/*****************************************************************************/ +/* Set current bands helpers */ + +gchar * +mm_telit_build_bnd_request (GArray *bands_array, + MMTelitBNDParseConfig *config, + GError **error) +{ + guint32 mask2g = 0; + guint64 mask3g = 0; + guint64 mask4g = 0; + guint64 mask4gext = 0; + guint i; + gint flag2g = -1; + gint64 flag3g = -1; + gint64 flag4g = -1; + gchar *cmd; + gboolean modem_is_2g = config->modem_is_2g; + gboolean modem_is_3g = config->modem_is_3g; + gboolean modem_is_4g = config->modem_is_4g; + + for (i = 0; i < bands_array->len; i++) { + MMModemBand band; + + band = g_array_index (bands_array, MMModemBand, i); + + /* Convert 2G bands into a bitmask, to match against telit_2g_to_mm_band_mask. */ + if (modem_is_2g && mm_common_band_is_gsm (band) && + (band >= MM_MODEM_BAND_TELIT_2G_FIRST) && (band <= MM_MODEM_BAND_TELIT_2G_LAST)) + mask2g += B2G_FLAG (band); + + /* Convert 3G bands into a bitmask, to match against telit_3g_to_mm_band_mask. We use + * a 64-bit explicit bitmask so that all values fit correctly. */ + if (modem_is_3g && mm_common_band_is_utran (band) && + (B3G_NUM (band) >= B3G_NUM (MM_MODEM_BAND_TELIT_3G_FIRST)) && (B3G_NUM (band) <= B3G_NUM (MM_MODEM_BAND_TELIT_3G_LAST))) + mask3g += B3G_FLAG (band); + + /* Convert 4G bands into a bitmask. We use a 64bit explicit bitmask so that + * all values fit correctly. */ + if (modem_is_4g && mm_common_band_is_eutran (band)) { + if (band >= MM_MODEM_BAND_TELIT_4G_FIRST && band <= MM_MODEM_BAND_TELIT_4G_LAST) + mask4g += B4G_FLAG (band); + else if (band >= MM_MODEM_BAND_TELIT_EXT_4G_FIRST && band <= MM_MODEM_BAND_TELIT_EXT_4G_LAST) + mask4gext += B4G_FLAG_EXT (band); + } + } + + /* Get 2G-specific telit value */ + if (mask2g) { + for (i = 0; i < G_N_ELEMENTS (telit_2g_to_mm_band_mask); i++) { + if (mask2g == telit_2g_to_mm_band_mask[i]) { + flag2g = i; + break; + } + } + if (flag2g == -1) { + gchar *bands_str; + + bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)(bands_array->data), bands_array->len); + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't find matching 2G bands Telit value for band combination: '%s'", bands_str); + g_free (bands_str); + return NULL; + } + } + + /* Get 3G-specific telit value */ + if (mask3g) { + const guint64 *telit_3g_to_mm_band_mask; + guint telit_3g_to_mm_band_mask_n_elements; + + initialize_telit_3g_to_mm_band_masks (); + + /* Select correct 3G band mask */ + if (config->modem_alternate_3g_bands) { + telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_alternate; + telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_alternate); + } else { + telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_default; + telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_default); + } + for (i = 0; i < telit_3g_to_mm_band_mask_n_elements; i++) { + if (mask3g == telit_3g_to_mm_band_mask[i]) { + flag3g = i; + break; + } + } + if (flag3g == -1) { + gchar *bands_str; + + bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)(bands_array->data), bands_array->len); + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't find matching 3G bands Telit value for band combination: '%s'", bands_str); + g_free (bands_str); + return NULL; + } + } + + /* Get 4G-specific telit band bitmask */ + flag4g = (mask4g != 0) ? ((gint64)mask4g) : -1; + + /* If the modem supports a given access tech, we must always give band settings + * for the specific tech */ + if (modem_is_2g && flag2g == -1) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "None or invalid 2G bands combination in the provided list"); + return NULL; + } + if (modem_is_3g && flag3g == -1) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "None or invalid 3G bands combination in the provided list"); + return NULL; + } + if (modem_is_4g && mask4g == 0 && mask4gext == 0) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "None or invalid 4G bands combination in the provided list"); + return NULL; + } + + if (modem_is_2g && !modem_is_3g && !modem_is_4g) + cmd = g_strdup_printf ("#BND=%d", flag2g); + else if (!modem_is_2g && modem_is_3g && !modem_is_4g) + cmd = g_strdup_printf ("#BND=0,%" G_GINT64_FORMAT, flag3g); + else if (modem_is_2g && modem_is_3g && !modem_is_4g) + cmd = g_strdup_printf ("#BND=%d,%" G_GINT64_FORMAT, flag2g, flag3g); + else if (!modem_is_2g && !modem_is_3g && modem_is_4g) { + if (!config->modem_ext_4g_bands) + cmd = g_strdup_printf ("#BND=0,0,%" G_GINT64_FORMAT, flag4g); + else + cmd = g_strdup_printf ("#BND=0,0,%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", mask4g, mask4gext); + } else if (!modem_is_2g && modem_is_3g && modem_is_4g) { + if (!config->modem_ext_4g_bands) + cmd = g_strdup_printf ("#BND=0,%" G_GINT64_FORMAT ",%" G_GINT64_FORMAT, flag3g, flag4g); + else + cmd = g_strdup_printf ("#BND=0,%" G_GINT64_FORMAT ",%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", flag3g, mask4g, mask4gext); + } else if (modem_is_2g && !modem_is_3g && modem_is_4g) { + if (!config->modem_ext_4g_bands) + cmd = g_strdup_printf ("#BND=%d,0,%" G_GINT64_FORMAT, flag2g, flag4g); + else + cmd = g_strdup_printf ("#BND=%d,0,%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", flag2g, mask4g, mask4gext); + } else if (modem_is_2g && modem_is_3g && modem_is_4g) { + if (!config->modem_ext_4g_bands) + cmd = g_strdup_printf ("#BND=%d,%" G_GINT64_FORMAT ",%" G_GINT64_FORMAT, flag2g, flag3g, flag4g); + else + cmd = g_strdup_printf ("#BND=%d,%" G_GINT64_FORMAT ",%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", flag2g, flag3g, mask4g, mask4gext); + } else + g_assert_not_reached (); + + return cmd; +} + +/*****************************************************************************/ +/* #BND response parser + * + * AT#BND=? + * #BND: <2G band flags range>,<3G band flags range>[, <4G band flags range>] + * + * where "band flags" is a list of numbers defining the supported bands. + * Note that the one Telit band flag may represent more than one MM band. + * + * e.g. + * + * #BND: (0-2),(3,4) + * + * (0,2) = 2G band flag 0 is EGSM + DCS + * = 2G band flag 1 is EGSM + PCS + * = 2G band flag 2 is DCS + G850 + * (3,4) = 3G band flag 3 is U2100 + U1900 + U850 + * = 3G band flag 4 is U1900 + U850 + * + * Modems that supports 4G bands, return a range value(X-Y) where + * X: represent the lower supported band, such as X = 2^(B-1), being B = B1, B2,..., B32 + * Y: is a 32 bit number resulting from a mask of all the supported bands: + * 1 - B1 + * 2 - B2 + * 4 - B3 + * 8 - B4 + * ... + * i - B(2exp(i-1)) + * ... + * 2147483648 - B32 + * + * e.g. + * (2-4106) + * 2 = 2^1 --> lower supported band B2 + * 4106 = 2^1 + 2^3 + 2^12 --> the supported bands are B2, B4, B13 + * + * + * AT#BND? + * #BND: <2G band flags>,<3G band flags>[, <4G band flags>] + * + * where "band flags" is a number defining the current bands. + * Note that the one Telit band flag may represent more than one MM band. + * + * e.g. + * + * #BND: 0,4 + * + * 0 = 2G band flag 0 is EGSM + DCS + * 4 = 3G band flag 4 is U1900 + U850 + * + * ---------------- + * + * For modems such as LN920 the #BND configuration/response for LTE bands is different + * from what is explained above: + * + * AT#BND=<GSM_band>[,<UMTS_band>[,<LTE_band>[,<LTE_band_ext>]]] + * + * <LTE_band>: hex: Indicates the LTE supported bands expressed as the sum of Band number (1+2+8 ...) calculated as shown in the table (mask of 64 bits): + * + * Band number(Hex) Band i + * 0 disable + * 1 B1 + * 2 B2 + * 4 B3 + * 8 B4 + * ... + * ... + * 80000000 B32 + * ... + * ... + * 800000000000 B48 + * + * It can take value, 0 - 87A03B0F38DF: range of the sum of Band number (1+2+4 ...) + * + * <LTE_band_ext>: hex: Indicates the LTE supported bands from B65 expressed as the sum of Band number (1+2+8 ...) calculated as shown in the table (mask of 64 bits): + * + * Band number(Hex) Band i + * 0 disable + * 2 B66 + * 40 B71 + * + * It can take value, 0 - 42: range of the sum of Band number (2+40) + * + * Note: LTE_band and LTE_band_ext cannot be 0 at the same time + * + * Example output: + * + * AT#BND=? + * #BND: (0),(0-11,17,18),(87A03B0F38DF),(42) + * + * AT#BND? + * #BND: 0,18,87A03B0F38DF,42 + * + */ + +static gboolean +telit_get_2g_mm_bands (GMatchInfo *match_info, + gpointer log_object, + GArray **bands, + GError **error) +{ + GError *inner_error = NULL; + GArray *values = NULL; + gchar *match_str = NULL; + guint i; + + match_str = g_match_info_fetch_named (match_info, "Bands2G"); + if (!match_str || match_str[0] == '\0') { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not find 2G band values from response"); + goto out; + } + + values = mm_parse_uint_list (match_str, &inner_error); + if (!values) + goto out; + + for (i = 0; i < values->len; i++) { + guint value; + + value = g_array_index (values, guint, i); + if (value < G_N_ELEMENTS (telit_2g_to_mm_band_mask)) { + guint j; + + for (j = MM_MODEM_BAND_TELIT_2G_FIRST; j <= MM_MODEM_BAND_TELIT_2G_LAST; j++) { + if ((telit_2g_to_mm_band_mask[value] & B2G_FLAG (j)) && !mm_common_bands_garray_lookup (*bands, j)) + *bands = g_array_append_val (*bands, j); + } + } else + mm_obj_dbg (log_object, "unhandled telit 2G band value configuration: %u", value); + } + +out: + g_free (match_str); + g_clear_pointer (&values, g_array_unref); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + return TRUE; +} + +static gboolean +telit_get_3g_mm_bands (GMatchInfo *match_info, + gpointer log_object, + gboolean modem_alternate_3g_bands, + GArray **bands, + GError **error) +{ + GError *inner_error = NULL; + GArray *values = NULL; + gchar *match_str = NULL; + guint i; + const guint64 *telit_3g_to_mm_band_mask; + guint telit_3g_to_mm_band_mask_n_elements; + + initialize_telit_3g_to_mm_band_masks (); + + /* Select correct 3G band mask */ + if (modem_alternate_3g_bands) { + telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_alternate; + telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_alternate); + } else { + telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_default; + telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_default); + } + + match_str = g_match_info_fetch_named (match_info, "Bands3G"); + if (!match_str || match_str[0] == '\0') { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not find 3G band values from response"); + goto out; + } + + values = mm_parse_uint_list (match_str, &inner_error); + if (!values) + goto out; + + for (i = 0; i < values->len; i++) { + guint value; + + value = g_array_index (values, guint, i); + + if (value < telit_3g_to_mm_band_mask_n_elements) { + guint j; + + for (j = 0; j < G_N_ELEMENTS (band_utran_index); j++) { + /* ignore non-3G bands */ + if (band_utran_index[j] == 0) + continue; + + if ((telit_3g_to_mm_band_mask[value] & B3G_FLAG (j)) && !mm_common_bands_garray_lookup (*bands, j)) + *bands = g_array_append_val (*bands, j); + } + } else + mm_obj_dbg (log_object, "unhandled telit 3G band value configuration: %u", value); + } + +out: + g_free (match_str); + g_clear_pointer (&values, g_array_unref); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + return TRUE; +} + +static gboolean +telit_get_4g_mm_bands (GMatchInfo *match_info, + GArray **bands, + GError **error) +{ + GError *inner_error = NULL; + MMModemBand band; + gchar *match_str = NULL; + guint64 value; + gchar **tokens = NULL; + gboolean hex_format = FALSE; + gboolean ok; + + match_str = g_match_info_fetch_named (match_info, "Bands4GDec"); + if (!match_str) { + match_str = g_match_info_fetch_named (match_info, "Bands4GHex"); + hex_format = match_str != NULL; + } + + if (!match_str || match_str[0] == '\0') { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not find 4G band flags from response"); + goto out; + } + + /* splitting will never return NULL as string is not empty */ + tokens = g_strsplit (match_str, "-", -1); + + /* If this is a range, get upper threshold, which contains the total supported mask */ + ok = hex_format? + mm_get_u64_from_hex_str (tokens[1] ? tokens[1] : tokens[0], &value): + mm_get_u64_from_str (tokens[1] ? tokens[1] : tokens[0], &value); + if (!ok) { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not parse 4G band mask from string: '%s'", match_str); + goto out; + } + + for (band = MM_MODEM_BAND_TELIT_4G_FIRST; band <= MM_MODEM_BAND_TELIT_4G_LAST; band++) { + if ((value & B4G_FLAG (band)) && !mm_common_bands_garray_lookup (*bands, band)) + g_array_append_val (*bands, band); + } + +out: + g_strfreev (tokens); + g_free (match_str); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + return TRUE; +} + +static gboolean +telit_get_ext_4g_mm_bands (GMatchInfo *match_info, + GArray **bands, + GError **error) +{ + GError *inner_error = NULL; + MMModemBand band; + gchar *match_str = NULL; + gchar *match_str_ext = NULL; + guint64 value; + + match_str = g_match_info_fetch_named (match_info, "Bands4GHex"); + if (!match_str || match_str[0] == '\0') { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not find 4G band hex mask flag from response"); + goto out; + } + + if (!mm_get_u64_from_hex_str (match_str, &value)) { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not parse 4G band hex mask from string: '%s'", match_str); + goto out; + } + + for (band = MM_MODEM_BAND_TELIT_4G_FIRST; band <= MM_MODEM_BAND_TELIT_4G_LAST; band++) { + if ((value & B4G_FLAG (band)) && !mm_common_bands_garray_lookup (*bands, band)) + g_array_append_val (*bands, band); + } + + /* extended bands */ + match_str_ext = g_match_info_fetch_named (match_info, "Bands4GExt"); + if (match_str_ext) { + + if (!mm_get_u64_from_hex_str (match_str_ext, &value)) { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not parse 4G ext band mask from string: '%s'", match_str_ext); + goto out; + } + + for (band = MM_MODEM_BAND_TELIT_EXT_4G_FIRST; band <= MM_MODEM_BAND_TELIT_EXT_4G_LAST; band++) { + if ((value & B4G_FLAG_EXT (band)) && !mm_common_bands_garray_lookup (*bands, band)) + g_array_append_val (*bands, band); + } + } + +out: + g_free (match_str); + g_free (match_str_ext); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + return TRUE; +} + +typedef enum { + LOAD_BANDS_TYPE_SUPPORTED, + LOAD_BANDS_TYPE_CURRENT, +} LoadBandsType; + +/* Regex tokens for #BND parsing */ +#define MM_SUPPORTED_BANDS_2G "\\s*\\((?P<Bands2G>[0-9\\-,]*)\\)" +#define MM_SUPPORTED_BANDS_3G "(,\\s*\\((?P<Bands3G>[0-9\\-,]*)\\))?" +#define MM_SUPPORTED_BANDS_4G_HEX "(,\\s*\\((?P<Bands4GHex>[0-9A-F\\-,]*)\\))?" +#define MM_SUPPORTED_BANDS_4G_DEC "(,\\s*\\((?P<Bands4GDec>[0-9\\-,]*)\\))?" +#define MM_SUPPORTED_BANDS_4G_EXT "(,\\s*\\((?P<Bands4GHex>[0-9A-F]+)\\))?(,\\s*\\((?P<Bands4GExt>[0-9A-F]+)\\))?" +#define MM_CURRENT_BANDS_2G "\\s*(?P<Bands2G>\\d+)" +#define MM_CURRENT_BANDS_3G "(,\\s*(?P<Bands3G>\\d+))?" +#define MM_CURRENT_BANDS_4G_HEX "(,\\s*(?P<Bands4GHex>[0-9A-F]+))?" +#define MM_CURRENT_BANDS_4G_DEC "(,\\s*(?P<Bands4GDec>\\d+))?" +#define MM_CURRENT_BANDS_4G_EXT "(,\\s*(?P<Bands4GHex>[0-9A-F]+))?(,\\s*(?P<Bands4GExt>[0-9A-F]+))?" + +static GArray * +common_parse_bnd_response (const gchar *response, + MMTelitBNDParseConfig *config, + LoadBandsType load_type, + gpointer log_object, + GError **error) +{ + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GRegex) r = NULL; + GError *inner_error = NULL; + GArray *bands = NULL; + const gchar *load_bands_regex = NULL; + + static const gchar *load_bands_regex_4g_hex[] = { + [LOAD_BANDS_TYPE_SUPPORTED] = "#BND:"MM_SUPPORTED_BANDS_2G MM_SUPPORTED_BANDS_3G MM_SUPPORTED_BANDS_4G_HEX, + [LOAD_BANDS_TYPE_CURRENT] = "#BND:"MM_CURRENT_BANDS_2G MM_CURRENT_BANDS_3G MM_CURRENT_BANDS_4G_HEX, + + }; + static const gchar *load_bands_regex_4g_dec[] = { + [LOAD_BANDS_TYPE_SUPPORTED] = "#BND:"MM_SUPPORTED_BANDS_2G MM_SUPPORTED_BANDS_3G MM_SUPPORTED_BANDS_4G_DEC, + [LOAD_BANDS_TYPE_CURRENT] = "#BND:"MM_CURRENT_BANDS_2G MM_CURRENT_BANDS_3G MM_CURRENT_BANDS_4G_DEC, + }; + static const gchar *load_bands_regex_4g_ext[] = { + [LOAD_BANDS_TYPE_SUPPORTED] = "#BND:"MM_SUPPORTED_BANDS_2G MM_SUPPORTED_BANDS_3G MM_SUPPORTED_BANDS_4G_EXT, + [LOAD_BANDS_TYPE_CURRENT] = "#BND:"MM_CURRENT_BANDS_2G MM_CURRENT_BANDS_3G MM_CURRENT_BANDS_4G_EXT, + }; + + if (config->modem_ext_4g_bands) + load_bands_regex = load_bands_regex_4g_ext[load_type]; + else if (config->modem_has_hex_format_4g_bands) + load_bands_regex = load_bands_regex_4g_hex[load_type]; + else + load_bands_regex = load_bands_regex_4g_dec[load_type]; + + r = g_regex_new (load_bands_regex, G_REGEX_RAW, 0, NULL); + g_assert (r); + + if (!g_regex_match (r, response, 0, &match_info)) { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not parse response '%s'", response); + goto out; + } + + if (!g_match_info_matches (match_info)) { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not find matches in response '%s'", response); + goto out; + } + + bands = g_array_new (TRUE, TRUE, sizeof (MMModemBand)); + + if (config->modem_is_2g && !telit_get_2g_mm_bands (match_info, log_object, &bands, &inner_error)) + goto out; + + if (config->modem_is_3g && !telit_get_3g_mm_bands (match_info, log_object, config->modem_alternate_3g_bands, &bands, &inner_error)) + goto out; + + if (config->modem_is_4g) { + gboolean ok; + + ok = config->modem_ext_4g_bands? + telit_get_ext_4g_mm_bands (match_info, &bands, &inner_error) : + telit_get_4g_mm_bands (match_info, &bands, &inner_error); + if (!ok) + goto out; + } +out: + if (inner_error) { + g_propagate_error (error, inner_error); + g_clear_pointer (&bands, g_array_unref); + return NULL; + } + + return bands; +} + +GArray * +mm_telit_parse_bnd_query_response (const gchar *response, + MMTelitBNDParseConfig *config, + gpointer log_object, + GError **error) +{ + return common_parse_bnd_response (response, + config, + LOAD_BANDS_TYPE_CURRENT, + log_object, + error); +} + +GArray * +mm_telit_parse_bnd_test_response (const gchar *response, + MMTelitBNDParseConfig *config, + gpointer log_object, + GError **error) +{ + return common_parse_bnd_response (response, + config, + LOAD_BANDS_TYPE_SUPPORTED, + log_object, + error); +} + +/*****************************************************************************/ +/* #QSS? response parser */ + +MMTelitQssStatus +mm_telit_parse_qss_query (const gchar *response, + GError **error) +{ + gint qss_status; + gint qss_mode; + + qss_status = QSS_STATUS_UNKNOWN; + if (sscanf (response, "#QSS: %d,%d", &qss_mode, &qss_status) != 2) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not parse \"#QSS?\" response: %s", response); + return QSS_STATUS_UNKNOWN; + } + + switch (qss_status) { + case QSS_STATUS_SIM_REMOVED: + case QSS_STATUS_SIM_INSERTED: + case QSS_STATUS_SIM_INSERTED_AND_UNLOCKED: + case QSS_STATUS_SIM_INSERTED_AND_READY: + return (MMTelitQssStatus) qss_status; + default: + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown QSS status value given: %d", qss_status); + return QSS_STATUS_UNKNOWN; + } +} + +/*****************************************************************************/ +/* Supported modes list helper */ + +GArray * +mm_telit_build_modes_list (void) +{ + GArray *combinations; + MMModemModeCombination mode; + + /* Build list of combinations for 3GPP devices */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 7); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 4G only */ + mode.allowed = MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 4G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G and 4G */ + mode.allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G, 3G and 4G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + return combinations; +} + +/*****************************************************************************/ +/* Software Package version response parser */ + +gchar * +mm_telit_parse_swpkgv_response (const gchar *response) +{ + gchar *version = NULL; + g_autofree gchar *base_version = NULL; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + guint matches; + + /* We are interested only in the first line of the response */ + r = g_regex_new ("(?P<Base>\\d{2}.\\d{2}.*)", + G_REGEX_RAW | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, + G_REGEX_MATCH_NEWLINE_CR, + NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL)) { + return NULL; + } + + matches = g_match_info_get_match_count (match_info); + if (matches < 2 || matches > 4) { + return NULL; + } + + base_version = g_match_info_fetch_named (match_info, "Base"); + if (base_version) + version = g_strdup (base_version); + + return version; +} + +/*****************************************************************************/ +/* MM Telit Model from revision string */ + +MMTelitModel +mm_telit_model_from_revision (const gchar *revision) +{ + guint i; + static const struct { + const gchar *revision_prefix; + MMTelitModel model; + } revision_to_model_map [] = { + {"24.01", MM_TELIT_MODEL_LM940}, + {"25.", MM_TELIT_MODEL_LE910C1}, + {"32.", MM_TELIT_MODEL_LM960}, + {"38.", MM_TELIT_MODEL_FN980}, + {"40.", MM_TELIT_MODEL_LN920}, + {"45.00", MM_TELIT_MODEL_FN990}, + }; + + if (!revision) + return MM_TELIT_MODEL_DEFAULT; + + for (i = 0; i < G_N_ELEMENTS (revision_to_model_map); ++i) { + if (g_str_has_prefix (revision, revision_to_model_map[i].revision_prefix)) + return revision_to_model_map[i].model; + } + + return MM_TELIT_MODEL_DEFAULT; +} + +static MMTelitSwRevCmp lm9x0_software_revision_cmp (const gchar *revision_a, + const gchar *revision_b) +{ + /* LM940 and LM960 share the same software revision format + * WW.XY.ABC[-ZZZZ], where WW is the chipset code and C the major version. + * If WW is the same, the other values X, Y, A and B are also the same, so + * we can limit the comparison to C only. ZZZZ is the minor version (it + * includes if version is beta, test, or alpha), but at this stage we are + * not interested in compare it. */ + guint chipset_a, chipset_b; + guint major_a, major_b; + guint x, y, a, b; + + g_return_val_if_fail ( + sscanf (revision_a, "%2u.%1u%1u.%1u%1u%1u", &chipset_a, &x, &y, &a, &b, &major_a) == 6, + MM_TELIT_SW_REV_CMP_INVALID); + g_return_val_if_fail ( + sscanf (revision_b, "%2u.%1u%1u.%1u%1u%1u", &chipset_b, &x, &y, &a, &b, &major_b) == 6, + MM_TELIT_SW_REV_CMP_INVALID); + + if (chipset_a != chipset_b) + return MM_TELIT_SW_REV_CMP_INVALID; + if (major_a > major_b) + return MM_TELIT_SW_REV_CMP_NEWER; + if (major_a < major_b) + return MM_TELIT_SW_REV_CMP_OLDER; + return MM_TELIT_SW_REV_CMP_EQUAL; +} + +MMTelitSwRevCmp mm_telit_software_revision_cmp (const gchar *revision_a, + const gchar *revision_b) +{ + MMTelitModel model_a; + MMTelitModel model_b; + + model_a = mm_telit_model_from_revision (revision_a); + model_b = mm_telit_model_from_revision (revision_b); + + if ((model_a == MM_TELIT_MODEL_LM940 || model_a == MM_TELIT_MODEL_LM960) && + (model_b == MM_TELIT_MODEL_LM940 || model_b == MM_TELIT_MODEL_LM960)) { + return lm9x0_software_revision_cmp (revision_a, revision_b); + } + + return MM_TELIT_SW_REV_CMP_UNSUPPORTED; +} diff --git a/src/plugins/telit/mm-modem-helpers-telit.h b/src/plugins/telit/mm-modem-helpers-telit.h new file mode 100644 index 00000000..38449769 --- /dev/null +++ b/src/plugins/telit/mm-modem-helpers-telit.h @@ -0,0 +1,90 @@ +/* -*- 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) 2015-2019 Telit. + * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es> + */ +#ifndef MM_MODEM_HELPERS_TELIT_H +#define MM_MODEM_HELPERS_TELIT_H + +#include <glib.h> +#include "ModemManager.h" + +typedef enum { + MM_TELIT_MODEL_DEFAULT, + MM_TELIT_MODEL_FN980, + MM_TELIT_MODEL_LE910C1, + MM_TELIT_MODEL_LM940, + MM_TELIT_MODEL_LM960, + MM_TELIT_MODEL_LN920, + MM_TELIT_MODEL_FN990, +} MMTelitModel; + +typedef struct { + gboolean modem_is_2g; + gboolean modem_is_3g; + gboolean modem_is_4g; + gboolean modem_alternate_3g_bands; + gboolean modem_has_hex_format_4g_bands; + gboolean modem_ext_4g_bands; +} MMTelitBNDParseConfig; + +typedef enum { + MM_TELIT_SW_REV_CMP_INVALID, + MM_TELIT_SW_REV_CMP_UNSUPPORTED, + MM_TELIT_SW_REV_CMP_OLDER, + MM_TELIT_SW_REV_CMP_EQUAL, + MM_TELIT_SW_REV_CMP_NEWER, +} MMTelitSwRevCmp; + +/* #BND response parsers and request builder */ +GArray *mm_telit_parse_bnd_query_response (const gchar *response, + MMTelitBNDParseConfig *config, + gpointer log_object, + GError **error); +GArray *mm_telit_parse_bnd_test_response (const gchar *response, + MMTelitBNDParseConfig *config, + gpointer log_object, + GError **error); +gchar *mm_telit_build_bnd_request (GArray *bands_array, + MMTelitBNDParseConfig *config, + GError **error); + +/* #QSS? response parser */ +typedef enum { /*< underscore_name=mm_telit_qss_status >*/ + QSS_STATUS_UNKNOWN = -1, + QSS_STATUS_SIM_REMOVED, + QSS_STATUS_SIM_INSERTED, + QSS_STATUS_SIM_INSERTED_AND_UNLOCKED, + QSS_STATUS_SIM_INSERTED_AND_READY, +} MMTelitQssStatus; + +MMTelitQssStatus mm_telit_parse_qss_query (const gchar *response, GError **error); + +/* CSIM lock state */ +typedef enum { /*< underscore_name=mm_telit_csim_lock_state >*/ + CSIM_LOCK_STATE_UNKNOWN, + CSIM_LOCK_STATE_UNLOCKED, + CSIM_LOCK_STATE_LOCK_REQUESTED, + CSIM_LOCK_STATE_LOCKED, +} MMTelitCsimLockState; + +GArray *mm_telit_build_modes_list (void); + +gchar *mm_telit_parse_swpkgv_response (const gchar *response); + +MMTelitModel mm_telit_model_from_revision (const gchar *revision); + +MMTelitSwRevCmp mm_telit_software_revision_cmp (const gchar *reference, + const gchar *revision); + +#endif /* MM_MODEM_HELPERS_TELIT_H */ diff --git a/src/plugins/telit/mm-plugin-telit.c b/src/plugins/telit/mm-plugin-telit.c new file mode 100644 index 00000000..d19966d6 --- /dev/null +++ b/src/plugins/telit/mm-plugin-telit.c @@ -0,0 +1,132 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2013 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-plugin-telit.h" +#include "mm-common-telit.h" +#include "mm-broadband-modem-telit.h" + + +#if defined WITH_QMI +# include "mm-broadband-modem-qmi.h" +#endif + +#if defined WITH_MBIM +# include "mm-broadband-modem-mbim-telit.h" +#endif + +G_DEFINE_TYPE (MMPluginTelit, mm_plugin_telit, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Telit modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered Telit modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_telit_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product, + subsystem_vendor)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_telit_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", "wwan", NULL }; + /* Vendors: Telit */ + static const guint16 vendor_ids[] = { 0x1bc7, 0 }; + static const mm_uint16_pair subsystem_vendor_ids[] = { + { 0x17cb, 0x1c5d }, /* FN990 */ + { 0, 0 } + }; + static const gchar *vendor_strings[] = { "telit", NULL }; + /* Custom init for port identification */ + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (telit_custom_init), + .finish = G_CALLBACK (telit_custom_init_finish), + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_TELIT, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_SUBSYSTEM_VENDOR_IDS, subsystem_vendor_ids, + MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + MM_PLUGIN_ALLOWED_QCDM, TRUE, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + NULL)); +} + +static void +mm_plugin_telit_init (MMPluginTelit *self) +{ +} + +static void +mm_plugin_telit_class_init (MMPluginTelitClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = telit_grab_port; +} diff --git a/src/plugins/telit/mm-plugin-telit.h b/src/plugins/telit/mm-plugin-telit.h new file mode 100644 index 00000000..0c61fbb8 --- /dev/null +++ b/src/plugins/telit/mm-plugin-telit.h @@ -0,0 +1,42 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2013 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_TELIT_H +#define MM_PLUGIN_TELIT_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_TELIT (mm_plugin_telit_get_type ()) +#define MM_PLUGIN_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_TELIT, MMPluginTelit)) +#define MM_PLUGIN_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_TELIT, MMPluginTelitClass)) +#define MM_IS_PLUGIN_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_TELIT)) +#define MM_IS_PLUGIN_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_TELIT)) +#define MM_PLUGIN_TELIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_TELIT, MMPluginTelitClass)) + +typedef struct { + MMPlugin parent; +} MMPluginTelit; + +typedef struct { + MMPluginClass parent; +} MMPluginTelitClass; + +GType mm_plugin_telit_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_TELIT_H */ diff --git a/src/plugins/telit/mm-shared-telit.c b/src/plugins/telit/mm-shared-telit.c new file mode 100644 index 00000000..09c122bb --- /dev/null +++ b/src/plugins/telit/mm-shared-telit.c @@ -0,0 +1,795 @@ +/* -*- 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 <dnlplm@gmail.com> + */ + +#include <config.h> + +#include <stdio.h> + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#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; + 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 (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_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; + + /* 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 + * <Telit Software Package Version>-<Production Parameters Version> + * <Modem FW Version> (Usually the same value returned by AT+GMR) + * <Production Parameters Version> + * <Application FW Version> + */ +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; +} + diff --git a/src/plugins/telit/mm-shared-telit.h b/src/plugins/telit/mm-shared-telit.h new file mode 100644 index 00000000..bf093ea5 --- /dev/null +++ b/src/plugins/telit/mm-shared-telit.h @@ -0,0 +1,107 @@ +/* -*- 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 <dnlplm@gmail.com> + */ + +#ifndef MM_SHARED_TELIT_H +#define MM_SHARED_TELIT_H + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" +#include "mm-modem-helpers-telit.h" + +#define MM_TYPE_SHARED_TELIT (mm_shared_telit_get_type ()) +#define MM_SHARED_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_TELIT, MMSharedTelit)) +#define MM_IS_SHARED_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_TELIT)) +#define MM_SHARED_TELIT_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_TELIT, MMSharedTelit)) + +typedef struct _MMSharedTelit MMSharedTelit; + +struct _MMSharedTelit { + GTypeInterface g_iface; + + /* Peek modem interface of the parent class of the object */ + MMIfaceModem * (* peek_parent_modem_interface) (MMSharedTelit *self); +}; + +GType mm_shared_telit_get_type (void); + +void mm_shared_telit_store_supported_modes (MMSharedTelit *self, + GArray *modes); + +gboolean mm_shared_telit_load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error); + +void mm_shared_telit_load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean mm_shared_telit_set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_shared_telit_set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data); + +void mm_shared_telit_modem_load_supported_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); + +GArray * mm_shared_telit_modem_load_supported_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_shared_telit_modem_load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); + +GArray * mm_shared_telit_modem_load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +gboolean mm_shared_telit_modem_set_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_shared_telit_modem_set_current_bands (MMIfaceModem *self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data); + +void mm_shared_telit_modem_load_revision (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); + +gchar * mm_shared_telit_modem_load_revision_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_shared_telit_store_revision (MMSharedTelit *self, + const gchar *revision); + +void mm_shared_telit_get_bnd_parse_config (MMIfaceModem *self, + MMTelitBNDParseConfig *config); +#endif /* MM_SHARED_TELIT_H */ diff --git a/src/plugins/telit/mm-shared.c b/src/plugins/telit/mm-shared.c new file mode 100644 index 00000000..ad2843dd --- /dev/null +++ b/src/plugins/telit/mm-shared.c @@ -0,0 +1,20 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-shared.h" + +MM_SHARED_DEFINE_MAJOR_VERSION +MM_SHARED_DEFINE_MINOR_VERSION +MM_SHARED_DEFINE_NAME(Telit) diff --git a/src/plugins/telit/tests/test-mm-modem-helpers-telit.c b/src/plugins/telit/tests/test-mm-modem-helpers-telit.c new file mode 100644 index 00000000..e14ba6ba --- /dev/null +++ b/src/plugins/telit/tests/test-mm-modem-helpers-telit.c @@ -0,0 +1,695 @@ +/* -*- 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) 2015-2019 Telit + * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es> + * + */ +#include <stdio.h> +#include <glib.h> +#include <glib-object.h> +#include <locale.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-telit.h" + +#include "test-helpers.h" + +/******************************************************************************/ + +#define MAX_BANDS_LIST_LEN 17 + +typedef struct { + const gchar *response; + MMTelitBNDParseConfig config; + guint mm_bands_len; + MMModemBand mm_bands [MAX_BANDS_LIST_LEN]; +} BndResponseTest; + +static BndResponseTest supported_band_mapping_tests [] = { + { + "#BND: (0-3)", {TRUE, FALSE, FALSE, FALSE, FALSE, FALSE}, 4, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850 } + }, + { + "#BND: (0-3),(0,2,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 7, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + { + "#BND: (0,3),(0,2,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 7, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + { + "#BND: (0,2),(0,2,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 6, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + { + "#BND: (0,2),(0-4,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 7, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + { + "#BND: (0-3),(0,2,5,6),(1-1)", {TRUE, TRUE, TRUE, FALSE, FALSE, FALSE}, 8, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_EUTRAN_1 } + }, + { + "#BND: (0),(0),(1-3)", {TRUE, TRUE, TRUE, FALSE, FALSE, FALSE}, 5, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2 } + }, + { + "#BND: (0),(0),(1-3)", {FALSE, FALSE, TRUE, FALSE, FALSE, FALSE}, 2, + { MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2 } + }, + /* 3G alternate band settings: default */ + { + "#BND: (0),(0,2,5,6,12,25)", {FALSE, TRUE, FALSE, FALSE, FALSE, FALSE}, 5, + { MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_UTRAN_6, + MM_MODEM_BAND_UTRAN_19 } + }, + /* 3G alternate band settings: alternate */ + { + "#BND: (0),(0,2,5,6,12,13)", {FALSE, TRUE, FALSE, TRUE, FALSE, FALSE}, 4, + { MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_3, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + /* ME910 (2G+4G device) + * 168695967: 0xA0E189F: 0000 1010 0000 1110 0001 1000 1001 1111 + */ + { + "#BND: (0-5),(0),(1-168695967)", {TRUE, FALSE, TRUE, FALSE, FALSE, FALSE}, 17, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2, + MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_4, + MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_12, + MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_18, + MM_MODEM_BAND_EUTRAN_19, + MM_MODEM_BAND_EUTRAN_20, + MM_MODEM_BAND_EUTRAN_26, + MM_MODEM_BAND_EUTRAN_28 } + }, + /* 4G ext band settings: devices such as LN920 */ + { + "#BND: (0),(0),(1003100185A),(42)", {FALSE, TRUE, TRUE, FALSE, TRUE, TRUE}, 13, + { MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_EUTRAN_2, + MM_MODEM_BAND_EUTRAN_4, + MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, + MM_MODEM_BAND_EUTRAN_12, + MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_25, + MM_MODEM_BAND_EUTRAN_29, + MM_MODEM_BAND_EUTRAN_30, + MM_MODEM_BAND_EUTRAN_41, + MM_MODEM_BAND_EUTRAN_66, + MM_MODEM_BAND_EUTRAN_71 } + }, + /* 4G band in hex format: devices such as LE910C1-EUX */ + { + "#BND: (0),(0,5,6,13,15,23),(80800C5)", {TRUE, TRUE, TRUE, FALSE, TRUE, FALSE}, 11, + { + MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_3, + MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_7, + MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_20, + MM_MODEM_BAND_EUTRAN_28 } + } +}; + +static void +test_parse_supported_bands_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (supported_band_mapping_tests); i++) { + GError *error = NULL; + GArray *bands = NULL; + + bands = mm_telit_parse_bnd_test_response (supported_band_mapping_tests[i].response, + &supported_band_mapping_tests[i].config, + NULL, + &error); + g_assert_no_error (error); + g_assert (bands); + + mm_test_helpers_compare_bands (bands, + supported_band_mapping_tests[i].mm_bands, + supported_band_mapping_tests[i].mm_bands_len); + g_array_unref (bands); + } +} + +static BndResponseTest current_band_mapping_tests [] = { + { + "#BND: 0", {TRUE, FALSE, FALSE, FALSE, FALSE, FALSE}, 2, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS } + }, + { + "#BND: 0,5", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 3, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_UTRAN_8 } + }, + { + "#BND: 1,3", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 5, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_5 } + }, + { + "#BND: 2,7", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 3, + { MM_MODEM_BAND_DCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_4 } + }, + { + "#BND: 3,0,1", {TRUE, TRUE, TRUE, FALSE, FALSE, FALSE}, 4, + { MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_EUTRAN_1 } + }, + { + "#BND: 0,0,3", {TRUE, FALSE, TRUE, FALSE, FALSE, FALSE}, 4, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2 } + }, + { + "#BND: 0,0,3", {FALSE, FALSE, TRUE, FALSE, FALSE, FALSE}, 2, + { MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2 } + }, + /* 3G alternate band settings: default */ + { + "#BND: 0,12", {FALSE, TRUE, FALSE, FALSE, FALSE, FALSE}, 1, + { MM_MODEM_BAND_UTRAN_6 } + }, + /* 3G alternate band settings: alternate */ + { + "#BND: 0,12", {FALSE, TRUE, FALSE, TRUE, FALSE, FALSE}, 4, + { MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_3, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + /* ME910 (2G+4G device) + * 168695967: 0xA0E189F: 0000 1010 0000 1110 0001 1000 1001 1111 + */ + { + "#BND: 5,0,168695967", {TRUE, FALSE, TRUE, FALSE, FALSE, FALSE}, 17, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2, + MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_4, + MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_12, + MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_18, + MM_MODEM_BAND_EUTRAN_19, + MM_MODEM_BAND_EUTRAN_20, + MM_MODEM_BAND_EUTRAN_26, + MM_MODEM_BAND_EUTRAN_28 } + }, + /* 4G ext band settings: devices such as LN920 */ + { + "#BND: 0,0,1003100185A,42", {FALSE, TRUE, TRUE, FALSE, FALSE, TRUE}, 13, + { MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_EUTRAN_2, + MM_MODEM_BAND_EUTRAN_4, + MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, + MM_MODEM_BAND_EUTRAN_12, + MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_25, + MM_MODEM_BAND_EUTRAN_29, + MM_MODEM_BAND_EUTRAN_30, + MM_MODEM_BAND_EUTRAN_41, + MM_MODEM_BAND_EUTRAN_66, + MM_MODEM_BAND_EUTRAN_71 } + } +}; + +static void +test_parse_current_bands_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (current_band_mapping_tests); i++) { + GError *error = NULL; + GArray *bands = NULL; + + bands = mm_telit_parse_bnd_query_response (current_band_mapping_tests[i].response, + ¤t_band_mapping_tests[i].config, + NULL, + &error); + g_assert_no_error (error); + g_assert (bands); + + mm_test_helpers_compare_bands (bands, + current_band_mapping_tests[i].mm_bands, + current_band_mapping_tests[i].mm_bands_len); + g_array_unref (bands); + } +} + +/******************************************************************************/ + +static void +test_common_bnd_cmd (const gchar *expected_cmd, + gboolean modem_is_2g, + gboolean modem_is_3g, + gboolean modem_is_4g, + gboolean modem_alternate_3g_bands, + gboolean modem_ext_4g_bands, + GArray *bands_array) +{ + gchar *cmd; + GError *error = NULL; + MMTelitBNDParseConfig config = { + .modem_is_2g = modem_is_2g, + .modem_is_3g = modem_is_3g, + .modem_is_4g = modem_is_4g, + .modem_alternate_3g_bands = modem_alternate_3g_bands, + .modem_ext_4g_bands = modem_ext_4g_bands + }; + + cmd = mm_telit_build_bnd_request (bands_array, &config, &error); + g_assert_no_error (error); + g_assert_cmpstr (cmd, ==, expected_cmd); + g_free (cmd); +} + +#define test_common_bnd_cmd_2g(EXPECTED_CMD, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, TRUE, FALSE, FALSE, FALSE, FALSE, BANDS_ARRAY) +#define test_common_bnd_cmd_3g(EXPECTED_CMD, ALTERNATE, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, FALSE, TRUE, FALSE, ALTERNATE, FALSE, BANDS_ARRAY) +#define test_common_bnd_cmd_4g(EXPECTED_CMD, EXTENDED, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, FALSE, FALSE, TRUE, FALSE, EXTENDED, BANDS_ARRAY) + +static void +test_common_bnd_cmd_error (gboolean modem_is_2g, + gboolean modem_is_3g, + gboolean modem_is_4g, + GArray *bands_array, + MMCoreError expected_error) +{ + gchar *cmd; + GError *error = NULL; + MMTelitBNDParseConfig config = { + .modem_is_2g = modem_is_2g, + .modem_is_3g = modem_is_3g, + .modem_is_4g = modem_is_4g, + .modem_alternate_3g_bands = FALSE, + .modem_ext_4g_bands = FALSE, + }; + cmd = mm_telit_build_bnd_request (bands_array, &config, &error); + g_assert_error (error, MM_CORE_ERROR, (gint)expected_error); + g_assert (!cmd); +} + +#define test_common_bnd_cmd_2g_invalid(BANDS_ARRAY) test_common_bnd_cmd_error (TRUE, FALSE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_FAILED) +#define test_common_bnd_cmd_3g_invalid(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, TRUE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_FAILED) +#define test_common_bnd_cmd_4g_invalid(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, FALSE, TRUE, BANDS_ARRAY, MM_CORE_ERROR_FAILED) +#define test_common_bnd_cmd_2g_not_found(BANDS_ARRAY) test_common_bnd_cmd_error (TRUE, FALSE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_NOT_FOUND) +#define test_common_bnd_cmd_3g_not_found(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, TRUE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_NOT_FOUND) +#define test_common_bnd_cmd_4g_not_found(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, FALSE, TRUE, BANDS_ARRAY, MM_CORE_ERROR_NOT_FOUND) + +static void +test_telit_get_2g_bnd_flag (void) +{ + GArray *bands_array; + MMModemBand egsm = MM_MODEM_BAND_EGSM; + MMModemBand dcs = MM_MODEM_BAND_DCS; + MMModemBand pcs = MM_MODEM_BAND_PCS; + MMModemBand g850 = MM_MODEM_BAND_G850; + MMModemBand u2100 = MM_MODEM_BAND_UTRAN_1; + MMModemBand eutran_i = MM_MODEM_BAND_EUTRAN_1; + + /* Test Flag 0 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, egsm); + g_array_append_val (bands_array, dcs); + test_common_bnd_cmd_2g ("#BND=0", bands_array); + g_array_unref (bands_array); + + /* Test flag 1 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, egsm); + g_array_append_val (bands_array, pcs); + test_common_bnd_cmd_2g ("#BND=1", bands_array); + g_array_unref (bands_array); + + /* Test flag 2 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, g850); + g_array_append_val (bands_array, dcs); + test_common_bnd_cmd_2g ("#BND=2", bands_array); + g_array_unref (bands_array); + + /* Test flag 3 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, g850); + g_array_append_val (bands_array, pcs); + test_common_bnd_cmd_2g ("#BND=3", bands_array); + g_array_unref (bands_array); + + /* Test invalid band array */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, g850); + g_array_append_val (bands_array, egsm); + test_common_bnd_cmd_2g_invalid (bands_array); + g_array_unref (bands_array); + + /* Test unmatched band array */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, u2100); + g_array_append_val (bands_array, eutran_i); + test_common_bnd_cmd_2g_not_found (bands_array); + g_array_unref (bands_array); +} + +static void +test_telit_get_3g_bnd_flag (void) +{ + GArray *bands_array; + MMModemBand u2100 = MM_MODEM_BAND_UTRAN_1; + MMModemBand u1900 = MM_MODEM_BAND_UTRAN_2; + MMModemBand u1800 = MM_MODEM_BAND_UTRAN_3; + MMModemBand u850 = MM_MODEM_BAND_UTRAN_5; + MMModemBand u800 = MM_MODEM_BAND_UTRAN_6; + MMModemBand u900 = MM_MODEM_BAND_UTRAN_8; + MMModemBand u17iv = MM_MODEM_BAND_UTRAN_4; + MMModemBand u17ix = MM_MODEM_BAND_UTRAN_9; + MMModemBand egsm = MM_MODEM_BAND_EGSM; + MMModemBand eutran_i = MM_MODEM_BAND_EUTRAN_1; + + /* Test flag 0 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u2100); + test_common_bnd_cmd_3g ("#BND=0,0", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,0", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 1 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u1900); + test_common_bnd_cmd_3g ("#BND=0,1", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,1", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 2 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u850); + test_common_bnd_cmd_3g ("#BND=0,2", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,2", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 3 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 3); + g_array_append_val (bands_array, u2100); + g_array_append_val (bands_array, u1900); + g_array_append_val (bands_array, u850); + test_common_bnd_cmd_3g ("#BND=0,3", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,3", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 4 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, u1900); + g_array_append_val (bands_array, u850); + test_common_bnd_cmd_3g ("#BND=0,4", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,4", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 5 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u900); + test_common_bnd_cmd_3g ("#BND=0,5", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,5", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 6 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, u2100); + g_array_append_val (bands_array, u900); + test_common_bnd_cmd_3g ("#BND=0,6", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,6", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 7 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u17iv); + test_common_bnd_cmd_3g ("#BND=0,7", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,7", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 12 in default */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u800); + test_common_bnd_cmd_3g ("#BND=0,12", FALSE, bands_array); + g_array_unref (bands_array); + + /* Test flag 12 in alternate */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4); + g_array_append_val (bands_array, u2100); + g_array_append_val (bands_array, u1800); + g_array_append_val (bands_array, u850); + g_array_append_val (bands_array, u900); + test_common_bnd_cmd_3g ("#BND=0,12", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test invalid band array */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u17ix); + test_common_bnd_cmd_3g_invalid (bands_array); + g_array_unref (bands_array); + + /* Test unmatched band array */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, egsm); + g_array_append_val (bands_array, eutran_i); + test_common_bnd_cmd_3g_not_found (bands_array); + g_array_unref (bands_array); +} + +static void +test_telit_get_4g_bnd_flag (void) +{ + GArray *bands_array; + MMModemBand eutran_i = MM_MODEM_BAND_EUTRAN_1; + MMModemBand eutran_ii = MM_MODEM_BAND_EUTRAN_2; + MMModemBand u2100 = MM_MODEM_BAND_UTRAN_1; + MMModemBand egsm = MM_MODEM_BAND_EGSM; + + /* Test flag 1 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, eutran_i); + test_common_bnd_cmd_4g ("#BND=0,0,1", FALSE, bands_array); + g_array_unref (bands_array); + + /* Test flag 3 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, eutran_i); + g_array_append_val (bands_array, eutran_ii); + test_common_bnd_cmd_4g ("#BND=0,0,3", FALSE, bands_array); + g_array_unref (bands_array); + + /* Test unmatched band array */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, egsm); + g_array_append_val (bands_array, u2100); + test_common_bnd_cmd_4g_not_found (bands_array); + g_array_unref (bands_array); +} + +/******************************************************************************/ + +typedef struct { + const char* response; + MMTelitQssStatus expected_qss; + const char *error_message; +} QssParseTest; + +static QssParseTest qss_parse_tests [] = { + {"#QSS: 0,0", QSS_STATUS_SIM_REMOVED, NULL}, + {"#QSS: 1,0", QSS_STATUS_SIM_REMOVED, NULL}, + {"#QSS: 0,1", QSS_STATUS_SIM_INSERTED, NULL}, + {"#QSS: 0,2", QSS_STATUS_SIM_INSERTED_AND_UNLOCKED, NULL}, + {"#QSS: 0,3", QSS_STATUS_SIM_INSERTED_AND_READY, NULL}, + {"#QSS:0,3", QSS_STATUS_SIM_INSERTED_AND_READY, NULL}, + {"#QSS: 0, 3", QSS_STATUS_SIM_INSERTED_AND_READY, NULL}, + {"#QSS: 0", QSS_STATUS_UNKNOWN, "Could not parse \"#QSS?\" response: #QSS: 0"}, + {"QSS:0,1", QSS_STATUS_UNKNOWN, "Could not parse \"#QSS?\" response: QSS:0,1"}, + {"#QSS: 0,5", QSS_STATUS_UNKNOWN, "Unknown QSS status value given: 5"}, +}; + +static void +test_telit_parse_qss_query (void) +{ + MMTelitQssStatus actual_qss_status; + GError *error = NULL; + guint i; + + for (i = 0; i < G_N_ELEMENTS (qss_parse_tests); i++) { + actual_qss_status = mm_telit_parse_qss_query (qss_parse_tests[i].response, &error); + + g_assert_cmpint (actual_qss_status, ==, qss_parse_tests[i].expected_qss); + if (qss_parse_tests[i].error_message) { + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED); + g_assert_cmpstr (error->message, ==, qss_parse_tests[i].error_message); + g_clear_error (&error); + } + } +} + +static void +test_telit_parse_swpkgv_response (void) +{ + static struct { + const gchar *response; + const gchar *expected; + } tt [] = { + {"\r\n12.34.567\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "12.34.567"}, + {"\r\n13.35.568-A123\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "13.35.568-A123"}, + {"\r\n14.36.569-B124\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "14.36.569-B124"}, + {"\r\n15.37.570-T125\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "15.37.570-T125"}, + {"\r\n16.38.571-P0F.224700\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "16.38.571-P0F.224700"}, + /* real example from LE910C1-EUX */ + {"\r\n25.30.224-B001-P0F.224700\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "25.30.224-B001-P0F.224700"}, + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS (tt); i++) { + gchar *actual = NULL; + + actual = mm_telit_parse_swpkgv_response(tt[i].response); + + g_assert_cmpstr (tt[i].expected, ==, actual); + g_free (actual); + } +} + +static void +test_telit_compare_software_revision_string (void) +{ + struct { + const char *revision_a; + const char *revision_b; + MMTelitSwRevCmp expected; + } tt [] = { + {"24.01.514", "24.01.514", MM_TELIT_SW_REV_CMP_EQUAL}, + {"24.01.514", "24.01.513", MM_TELIT_SW_REV_CMP_NEWER}, + {"24.01.513", "24.01.514", MM_TELIT_SW_REV_CMP_OLDER}, + {"32.00.013", "24.01.514", MM_TELIT_SW_REV_CMP_INVALID}, + {"32.00.014", "32.00.014", MM_TELIT_SW_REV_CMP_EQUAL}, + {"32.00.014", "32.00.013", MM_TELIT_SW_REV_CMP_NEWER}, + {"32.00.013", "32.00.014", MM_TELIT_SW_REV_CMP_OLDER}, + {"38.00.000", "38.00.000", MM_TELIT_SW_REV_CMP_UNSUPPORTED}, + /* LM9x0 Minor version (e.g. beta, test, alpha) value is currently + * ignored because not required by any implemented feature. */ + {"24.01.516-B123", "24.01.516-B134", MM_TELIT_SW_REV_CMP_EQUAL}, + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS (tt); i++) { + g_assert_cmpint (tt[i].expected, + ==, + mm_telit_software_revision_cmp (tt[i].revision_a, tt[i].revision_b)); + } +} + +/******************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/telit/bands/supported/parse_bands_response", test_parse_supported_bands_response); + g_test_add_func ("/MM/telit/bands/current/parse_bands_response", test_parse_current_bands_response); + g_test_add_func ("/MM/telit/bands/current/set_bands/2g", test_telit_get_2g_bnd_flag); + g_test_add_func ("/MM/telit/bands/current/set_bands/3g", test_telit_get_3g_bnd_flag); + g_test_add_func ("/MM/telit/bands/current/set_bands/4g", test_telit_get_4g_bnd_flag); + g_test_add_func ("/MM/telit/qss/query", test_telit_parse_qss_query); + g_test_add_func ("/MM/telit/swpkv/parse_response", test_telit_parse_swpkgv_response); + g_test_add_func ("/MM/telit/revision/compare", test_telit_compare_software_revision_string); + return g_test_run (); +} diff --git a/src/plugins/tests/gsm-port.conf b/src/plugins/tests/gsm-port.conf new file mode 100644 index 00000000..ae157834 --- /dev/null +++ b/src/plugins/tests/gsm-port.conf @@ -0,0 +1,46 @@ + + +AT \r\nOK\r\n +ATE0 \r\nOK\r\n +ATV1 \r\nOK\r\n +AT+CMEE=1 \r\nOK\r\n +ATX4 \r\nOK\r\n +AT&C1 \r\nOK\r\n +AT+IFC=1,1 \r\nOK\r\n +AT+GCAP \r\n+GCAP: +CGSM +DS +ES\r\n\r\nOK\r\n +ATI \r\nManufacturer: Some vendor\r\nModel: Some model\r\nRevision: Some revision\r\nIMEI: 001100110011002<CR><LF>+GCAP: +CGSM,+DS,+ES\r\n\r\nOK\r\n +AT+WS46=? \r\n+WS46: (12,22)\r\n\r\nOK\r\n +AT+CGMI \r\nSome vendor\r\n\r\nOK\r\n +AT+CGMM \r\nSome model\r\n\r\nOK\r\n +AT+CGMR \r\nSome revision\r\n\r\nOK\r\n +AT+CGSN \r\n123456789012345\r\n\r\nOK\r\n +AT+CGDCONT=? \r\n+CGDCONT: (1-11),"IP",,,(0-2),(0-3)\r\n+CGDCONT: (1-11),"IPV6",,,(0-2),(0-3)\r\n+CGDCONT: (1-11),"IPV4V6",,,(0-2),(0-3)\r\n+CGDCONT: (1-11),"PPP",,,(0-2),(0-3)\r\n\r\nOK\r\n +AT+CIMI \r\n998899889988997\r\n\r\nOK\r\n +AT+CLCK=? \r\n+CLCK: ("SC","AO","OI","OX","AI","IR","AB","AG","AC","PS","FD")\r\n\r\nOK\r\n +AT+CLCK="SC",2 \r\n+CLCK: 1\r\n\r\nOK\r\n +AT+CLCK="FD",2 \r\n+CLCK: 1\r\n\r\nOK\r\n +AT+CLCK="PS",2 \r\n+CLCK: 1\r\n\r\nOK\r\n +AT+CFUN? \r\n+CFUN: 1\r\n\r\nOK\r\n +AT+CSCS=? \r\n+CSCS: ("IRA","UCS2","GSM")\r\n\r\nOK\r\n +AT+CSCS="UCS2" \r\nOK\r\n +AT+CSCS? \r\n+CSCS: "UCS2"\r\n\r\nOK\r\n +AT+CREG=2 \r\nOK\r\n +AT+CGREG=2 \r\nOK\r\n +AT+CREG=0 \r\nOK\r\n +AT+CGREG=0 \r\nOK\r\n +AT+CREG? \r\n+CREG: 2,1,"1234","001122BB"\r\n\r\nOK\r\n +AT+CGREG? \r\n+CGREG: 2,1,"31C5","0083F7CD"\r\n\r\nOK\r\n +AT+COPS=3,2;+COPS? \r\n+COPS: 0,2,"21401",2\r\n\r\nOK\r\n +AT+COPS=3,0;+COPS? \r\n+COPS: 0,0,"vodafone ES"\r\n\r\nOK\r\n +AT+CMGF=? \r\n+CMGF: (0,1)\r\n\r\nOK\r\n +AT+CMGF=0 \r\nOK\r\n +AT+CSQ \r\n+CSQ: 17,99\r\n\r\nOK\r\n + +# By default, no PIN required +AT+CPIN? \r\n+CPIN: READY\r\n\r\nOK\r\n + +# By default, no messaging support +AT+CNMI=? \r\nERROR\r\n + +# By default, no USSD support +AT+CUSD=? \r\nERROR\r\n diff --git a/src/plugins/tests/test-fixture.c b/src/plugins/tests/test-fixture.c new file mode 100644 index 00000000..29eb8d55 --- /dev/null +++ b/src/plugins/tests/test-fixture.c @@ -0,0 +1,163 @@ +/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org> + */ + +#include "test-fixture.h" + +void +test_fixture_setup (TestFixture *fixture) +{ + GError *error = NULL; + GVariant *result; + + /* Create the global dbus-daemon for this test suite */ + fixture->dbus = g_test_dbus_new (G_TEST_DBUS_NONE); + + /* Add the private directory with our in-tree service files, + * TEST_SERVICES is defined by the build system to point + * to the right directory. */ + g_test_dbus_add_service_dir (fixture->dbus, TEST_SERVICES); + + /* Start the private DBus daemon */ + g_test_dbus_up (fixture->dbus); + + /* Create DBus connection */ + fixture->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (fixture->connection == NULL) + g_error ("Error getting connection to test bus: %s", error->message); + + /* Ping to autostart MM; wait up to 30s */ + result = g_dbus_connection_call_sync (fixture->connection, + "org.freedesktop.ModemManager1", + "/org/freedesktop/ModemManager1", + "org.freedesktop.DBus.Peer", + "Ping", + NULL, /* inputs */ + NULL, /* outputs */ + G_DBUS_CALL_FLAGS_NONE, + 30000, /* timeout, ms */ + NULL, /* cancellable */ + &error); + if (!result) + g_error ("Error starting ModemManager in test bus: %s", error->message); + g_variant_unref (result); + + /* Create the proxy that we're going to test */ + fixture->test = mm_gdbus_test_proxy_new_sync (fixture->connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.freedesktop.ModemManager1", + "/org/freedesktop/ModemManager1", + NULL, /* cancellable */ + &error); + if (fixture->test == NULL) + g_error ("Error getting ModemManager test proxy: %s", error->message); +} + +void +test_fixture_teardown (TestFixture *fixture) +{ + g_object_unref (fixture->connection); + + /* Tear down the proxy */ + if (fixture->test) + g_object_unref (fixture->test); + + /* Stop the private D-Bus daemon; stopping the bus will stop MM as well */ + g_test_dbus_down (fixture->dbus); + g_object_unref (fixture->dbus); +} + +void +test_fixture_set_profile (TestFixture *fixture, + const gchar *profile_name, + const gchar *plugin, + const gchar *const *ports) +{ + GError *error = NULL; + + /* Set the test profile */ + g_assert (fixture->test != NULL); + if (!mm_gdbus_test_call_set_profile_sync (fixture->test, + profile_name, + plugin, + ports, + NULL, /* cancellable */ + &error)) + g_error ("Error setting test profile: %s", error->message); +} + +static MMObject * +common_get_modem (TestFixture *fixture, + gboolean modem_expected) +{ + MMObject *found = NULL; + guint wait_time = 0; + + /* Find new modem object */ + while (TRUE) { + GError *error = NULL; + MMManager *manager; + GList *modems; + guint n_modems; + gboolean ready = FALSE; + + /* Create manager on each loop, so that we don't require on an external + * global main context processing to receive the DBus property updates. + */ + g_assert (fixture->connection != NULL); + manager = mm_manager_new_sync (fixture->connection, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, + NULL, /* cancellable */ + &error); + if (!manager) + g_error ("Couldn't create manager: %s", error->message); + + modems = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (manager)); + n_modems = g_list_length (modems); + g_assert_cmpuint (n_modems, <=, 1); + + if ((guint)modem_expected == n_modems) { + if (modems) { + found = MM_OBJECT (g_object_ref (modems->data)); + g_message ("Found modem at '%s'", mm_object_get_path (found)); + } + ready = TRUE; + } + + g_list_free_full (modems, g_object_unref); + g_object_unref (manager); + + if (ready) + break; + + /* Blocking wait */ + g_assert_cmpuint (wait_time, <=, 20); + wait_time++; + sleep (1); + } + + return found; +} + +MMObject * +test_fixture_get_modem (TestFixture *fixture) +{ + return common_get_modem (fixture, TRUE); +} + +void +test_fixture_no_modem (TestFixture *fixture) +{ + common_get_modem (fixture, FALSE); +} diff --git a/src/plugins/tests/test-fixture.h b/src/plugins/tests/test-fixture.h new file mode 100644 index 00000000..b6c24379 --- /dev/null +++ b/src/plugins/tests/test-fixture.h @@ -0,0 +1,54 @@ +/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef TEST_FIXTURE_H +#define TEST_FIXTURE_H + +#include <gio/gio.h> +#include <libmm-glib.h> +#include "mm-gdbus-test.h" + +/*****************************************************************************/ +/* Test fixture setup */ + +/* The fixture contains a GTestDBus object and + * a proxy to the service we're going to be testing. + */ +typedef struct { + GTestDBus *dbus; + MmGdbusTest *test; + GDBusConnection *connection; +} TestFixture; + +void test_fixture_setup (TestFixture *fixture); +void test_fixture_teardown (TestFixture *fixture); + +typedef void (*TCFunc) (TestFixture *, gconstpointer); +#define TEST_ADD(path,method) \ + g_test_add (path, \ + TestFixture, \ + NULL, \ + (TCFunc)test_fixture_setup, \ + (TCFunc)method, \ + (TCFunc)test_fixture_teardown) + +void test_fixture_set_profile (TestFixture *fixture, + const gchar *profile_name, + const gchar *plugin, + const gchar *const *ports); +MMObject *test_fixture_get_modem (TestFixture *fixture); +void test_fixture_no_modem (TestFixture *fixture); + +#endif /* TEST_FIXTURE_H */ diff --git a/src/plugins/tests/test-helpers.c b/src/plugins/tests/test-helpers.c new file mode 100644 index 00000000..8148d908 --- /dev/null +++ b/src/plugins/tests/test-helpers.c @@ -0,0 +1,52 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log.h" +#include "mm-modem-helpers.h" + +#include "test-helpers.h" + +void +mm_test_helpers_compare_bands (GArray *bands, + const MMModemBand *expected_bands, + guint n_expected_bands) +{ + gchar *bands_str; + GArray *expected_bands_array; + gchar *expected_bands_str; + + if (!expected_bands || !n_expected_bands) { + g_assert (!bands); + return; + } + + g_assert (bands); + mm_common_bands_garray_sort (bands); + bands_str = mm_common_build_bands_string ((MMModemBand *)(gpointer)(bands->data), bands->len); + + expected_bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_expected_bands); + g_array_append_vals (expected_bands_array, expected_bands, n_expected_bands); + mm_common_bands_garray_sort (expected_bands_array); + expected_bands_str = mm_common_build_bands_string ((MMModemBand *)(gpointer)(expected_bands_array->data), expected_bands_array->len); + g_array_unref (expected_bands_array); + + g_assert_cmpstr (bands_str, ==, expected_bands_str); + g_free (bands_str); + g_free (expected_bands_str); +} diff --git a/src/plugins/tests/test-helpers.h b/src/plugins/tests/test-helpers.h new file mode 100644 index 00000000..8362754c --- /dev/null +++ b/src/plugins/tests/test-helpers.h @@ -0,0 +1,26 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef TEST_HELPERS_H +#define TEST_HELPERS_H + +#include <glib.h> +#include <libmm-glib.h> + +void mm_test_helpers_compare_bands (GArray *bands, + const MMModemBand *expected_bands, + guint n_expected_bands); + +#endif /* TEST_HELPERS_H */ diff --git a/src/plugins/tests/test-keyfiles.c b/src/plugins/tests/test-keyfiles.c new file mode 100644 index 00000000..f92df9c3 --- /dev/null +++ b/src/plugins/tests/test-keyfiles.c @@ -0,0 +1,79 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ +#include <config.h> + +#include <glib.h> +#include <glib-object.h> +#include <string.h> +#include <stdio.h> +#include <locale.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" + +/************************************************************/ + +static void +common_test (const gchar *keyfile_path) +{ + GKeyFile *keyfile; + GError *error = NULL; + gboolean ret; + + if (!keyfile_path) + return; + + keyfile = g_key_file_new (); + ret = g_key_file_load_from_file (keyfile, keyfile_path, G_KEY_FILE_NONE, &error); + g_assert_no_error (error); + g_assert (ret); + g_key_file_unref (keyfile); +} + +/* Placeholder test to avoid compiler warning about common_test() being unused + * when none of the plugins enabled in build have custom key files. */ +static void +test_placeholder (void) +{ + common_test (NULL); +} + +/************************************************************/ + +#if defined ENABLE_PLUGIN_FOXCONN +static void +test_foxconn_t77w968 (void) +{ + common_test (TESTKEYFILE_FOXCONN_T77W968); +} +#endif + +/************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/MM/test-keyfiles/placeholder", test_placeholder); + +#if defined ENABLE_PLUGIN_FOXCONN + g_test_add_func ("/MM/test-keyfiles/foxconn/t77w968", test_foxconn_t77w968); +#endif + + return g_test_run (); +} diff --git a/src/plugins/tests/test-port-context.c b/src/plugins/tests/test-port-context.c new file mode 100644 index 00000000..e96cff7b --- /dev/null +++ b/src/plugins/tests/test-port-context.c @@ -0,0 +1,422 @@ +/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <gio/gio.h> +#include <gio/gunixsocketaddress.h> +#include <string.h> + +#include "test-port-context.h" + +#define BUFFER_SIZE 1024 + +struct _TestPortContext { + gchar *name; + GThread *thread; + gboolean ready; + GCond ready_cond; + GMutex ready_mutex; + GMainLoop *loop; + GMainContext *context; + GSocket *socket; + GSocketService *socket_service; + GList *clients; + GHashTable *commands; +}; + +/*****************************************************************************/ + +void +test_port_context_set_command (TestPortContext *self, + const gchar *command, + const gchar *response) +{ + if (G_UNLIKELY (!self->commands)) + self->commands = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_replace (self->commands, g_strdup (command), g_strcompress (response)); +} + +void +test_port_context_load_commands (TestPortContext *self, + const gchar *file) +{ + GError *error = NULL; + gchar *contents; + gchar *current; + + if (!g_file_get_contents (file, &contents, NULL, &error)) + g_error ("Couldn't load commands file '%s': %s", + g_filename_display_name (file), + error->message); + + current = contents; + while (current) { + gchar *next; + + next = strchr (current, '\n'); + if (next) { + *next = '\0'; + next++; + } + + g_strstrip (current); + if (current[0] != '\0' && current[0] != '#') { + gchar *response; + + response = current; + while (*response != ' ') + response++; + g_assert (*response == ' '); + *response = '\0'; + response++; + while (*response == ' ') + response++; + g_assert (*response != '\0'); + + test_port_context_set_command (self, current, response); + } + current = next; + } + + g_free (contents); +} + +static const gchar * +process_next_command (TestPortContext *ctx, + GByteArray *buffer) +{ + gsize i = 0; + gchar *command; + const gchar *response; + static const gchar *error_response = "\r\nERROR\r\n"; + + /* Find command end */ + while (i < buffer->len && buffer->data[i] != '\r' && buffer->data[i] != '\n') + i++; + if (i == buffer->len) + /* no command */ + return NULL; + + while (i < buffer->len && (buffer->data[i] == '\r' || buffer->data[i] == '\n')) + buffer->data[i++] = '\0'; + + /* Setup command and lookup response */ + command = g_strndup ((gchar *)buffer->data, i); + response = g_hash_table_lookup (ctx->commands, command); + g_free (command); + + /* Remove command from buffer */ + g_byte_array_remove_range (buffer, 0, i); + + return response ? response : error_response; +} + +/*****************************************************************************/ + +typedef struct { + TestPortContext *ctx; + GSocketConnection *connection; + GSource *connection_readable_source; + GByteArray *buffer; +} Client; + +static void +client_free (Client *client) +{ + g_source_destroy (client->connection_readable_source); + g_source_unref (client->connection_readable_source); + g_output_stream_close (g_io_stream_get_output_stream (G_IO_STREAM (client->connection)), NULL, NULL); + if (client->buffer) + g_byte_array_unref (client->buffer); + g_object_unref (client->connection); + g_slice_free (Client, client); +} + +static void +connection_close (Client *client) +{ + client->ctx->clients = g_list_remove (client->ctx->clients, client); + client_free (client); +} + +static void +client_parse_request (Client *client) +{ + const gchar *response; + + do { + response = process_next_command (client->ctx, client->buffer); + if (response) { + GError *error = NULL; + + if (!g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (client->connection)), + response, + strlen (response), + NULL, /* bytes_written */ + NULL, /* cancellable */ + &error)) { + g_warning ("Cannot send response to client: %s", error->message); + g_error_free (error); + } + } + + } while (response); +} + +static gboolean +connection_readable_cb (GSocket *socket, + GIOCondition condition, + Client *client) +{ + guint8 buffer[BUFFER_SIZE]; + GError *error = NULL; + gssize r; + + if (condition & G_IO_HUP || condition & G_IO_ERR) { + g_debug ("client connection closed"); + connection_close (client); + return FALSE; + } + + if (!(condition & G_IO_IN || condition & G_IO_PRI)) + return TRUE; + + r = g_input_stream_read (g_io_stream_get_input_stream (G_IO_STREAM (client->connection)), + buffer, + BUFFER_SIZE, + NULL, + &error); + + if (r < 0) { + g_warning ("Error reading from istream: %s", error ? error->message : "unknown"); + if (error) + g_error_free (error); + /* Close the device */ + connection_close (client); + return FALSE; + } + + if (r == 0) + return TRUE; + + /* else, r > 0 */ + if (!G_UNLIKELY (client->buffer)) + client->buffer = g_byte_array_sized_new (r); + g_byte_array_append (client->buffer, buffer, r); + + /* Try to parse input messages */ + client_parse_request (client); + + return TRUE; +} + +static Client * +client_new (TestPortContext *self, + GSocketConnection *connection) +{ + Client *client; + + client = g_slice_new0 (Client); + client->ctx = self; + client->connection = g_object_ref (connection); + client->connection_readable_source = g_socket_create_source (g_socket_connection_get_socket (client->connection), + G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP, + NULL); + g_source_set_callback (client->connection_readable_source, + (GSourceFunc)connection_readable_cb, + client, + NULL); + g_source_attach (client->connection_readable_source, self->context); + + return client; +} + +/* /\*****************************************************************************\/ */ + +static void +incoming_cb (GSocketService *service, + GSocketConnection *connection, + GObject *unused, + TestPortContext *self) +{ + Client *client; + + client = client_new (self, connection); + self->clients = g_list_append (self->clients, client); +} + +static void +create_socket_service (TestPortContext *self) +{ + GError *error = NULL; + GSocketService *service; + GSocketAddress *address; + GSocket *socket; + + g_assert (self->socket_service == NULL); + + /* Create socket */ + socket = g_socket_new (G_SOCKET_FAMILY_UNIX, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + if (!socket) + g_error ("Cannot create socket: %s", error->message); + + /* Bind to address */ + address = (g_unix_socket_address_new_with_type ( + self->name, + -1, + (g_str_has_prefix (self->name, "abstract:") ? + G_UNIX_SOCKET_ADDRESS_ABSTRACT : + G_UNIX_SOCKET_ADDRESS_PATH))); + if (!g_socket_bind (socket, address, TRUE, &error)) + g_error ("Cannot bind socket: %s", error->message); + g_object_unref (address); + + /* Listen */ + if (!g_socket_listen (socket, &error)) + g_error ("Cannot listen in socket: %s", error->message); + + /* Create socket service */ + service = g_socket_service_new (); + g_signal_connect (service, "incoming", G_CALLBACK (incoming_cb), self); + if (!g_socket_listener_add_socket (G_SOCKET_LISTENER (service), + socket, + NULL, /* don't pass an object, will take a reference */ + &error)) + g_error ("Cannot add listener to socket: %s", error->message); + + /* Start it */ + g_socket_service_start (service); + + /* And store both the service and the socket. + * Since GLib 2.42 the socket may not be explicitly closed when the + * listener is diposed, so we'll do it ourselves. */ + self->socket_service = service; + self->socket = socket; + + /* Signal that the thread is ready */ + g_mutex_lock (&self->ready_mutex); + self->ready = TRUE; + g_cond_signal (&self->ready_cond); + g_mutex_unlock (&self->ready_mutex); +} + +/*****************************************************************************/ + +static gboolean +cancel_loop_cb (TestPortContext *self) +{ + g_main_loop_quit (self->loop); + return FALSE; +} + +void +test_port_context_stop (TestPortContext *self) +{ + g_assert (self->thread != NULL); + g_assert (self->loop != NULL); + g_assert (self->context != NULL); + + /* Cancel main loop of the port context thread, by scheduling an idle task + * in the thread-owned main context */ + g_main_context_invoke (self->context, (GSourceFunc) cancel_loop_cb, self); + + g_thread_join (self->thread); + self->thread = NULL; +} + +static gpointer +port_context_thread_func (TestPortContext *self) +{ + g_assert (self->loop == NULL); + g_assert (self->context == NULL); + + /* Define main context and loop for the thread */ + self->context = g_main_context_new (); + self->loop = g_main_loop_new (self->context, FALSE); + g_main_context_push_thread_default (self->context); + + /* Once the thread default context is setup, launch service */ + create_socket_service (self); + + g_main_loop_run (self->loop); + + g_main_loop_unref (self->loop); + self->loop = NULL; + g_main_context_unref (self->context); + self->context = NULL; + return NULL; +} + +void +test_port_context_start (TestPortContext *self) +{ + g_assert (self->thread == NULL); + self->thread = g_thread_new (self->name, + (GThreadFunc)port_context_thread_func, + self); + + /* Now wait until the thread has finished its initialization and is + * ready to serve connections */ + g_mutex_lock (&self->ready_mutex); + while (!self->ready) + g_cond_wait (&self->ready_cond, &self->ready_mutex); + g_mutex_unlock (&self->ready_mutex); +} + +/*****************************************************************************/ + +void +test_port_context_free (TestPortContext *self) +{ + g_assert (self->thread == NULL); + g_assert (self->loop == NULL); + + g_cond_clear (&self->ready_cond); + g_mutex_clear (&self->ready_mutex); + + if (self->commands) + g_hash_table_unref (self->commands); + g_list_free_full (self->clients, (GDestroyNotify)client_free); + if (self->socket) { + GError *error = NULL; + + if (!g_socket_close (self->socket, &error)) { + g_debug ("Couldn't close socket: %s", error->message); + g_error_free (error); + } + g_object_unref (self->socket); + } + if (self->socket_service) { + if (g_socket_service_is_active (self->socket_service)) + g_socket_service_stop (self->socket_service); + g_object_unref (self->socket_service); + } + g_free (self->name); + g_slice_free (TestPortContext, self); +} + +TestPortContext * +test_port_context_new (const gchar *name) +{ + TestPortContext *self; + + self = g_slice_new0 (TestPortContext); + self->name = g_strdup (name); + g_cond_init (&self->ready_cond); + g_mutex_init (&self->ready_mutex); + return self; +} diff --git a/src/plugins/tests/test-port-context.h b/src/plugins/tests/test-port-context.h new file mode 100644 index 00000000..9aaf1077 --- /dev/null +++ b/src/plugins/tests/test-port-context.h @@ -0,0 +1,35 @@ +/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef TEST_PORT_CONTEXT_H +#define TEST_PORT_CONTEXT_H + +#include <glib.h> +#include <glib-object.h> + +typedef struct _TestPortContext TestPortContext; + +TestPortContext *test_port_context_new (const gchar *name); +void test_port_context_start (TestPortContext *self); +void test_port_context_stop (TestPortContext *self); +void test_port_context_free (TestPortContext *self); + +void test_port_context_set_command (TestPortContext *self, + const gchar *command, + const gchar *response); +void test_port_context_load_commands (TestPortContext *self, + const gchar *commands_file); + +#endif /* TEST_PORT_CONTEXT_H */ diff --git a/src/plugins/tests/test-udev-rules.c b/src/plugins/tests/test-udev-rules.c new file mode 100644 index 00000000..fe11c783 --- /dev/null +++ b/src/plugins/tests/test-udev-rules.c @@ -0,0 +1,257 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <glib.h> +#include <glib-object.h> +#include <string.h> +#include <stdio.h> +#include <locale.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-kernel-device-generic-rules.h" +#include "mm-log-test.h" + +/************************************************************/ + +static void +common_test (const gchar *plugindir) +{ + GArray *rules; + GError *error = NULL; + + if (!plugindir) + return; + + rules = mm_kernel_device_generic_rules_load (plugindir, &error); + g_assert_no_error (error); + g_assert (rules); + g_assert (rules->len > 0); + + g_array_unref (rules); +} + +/* Placeholder test to avoid compiler warning about common_test() being unused + * when none of the plugins enabled in build have custom udev rules. */ +static void +test_placeholder (void) +{ + common_test (NULL); +} + +/************************************************************/ + +#if defined ENABLE_PLUGIN_HUAWEI +static void +test_huawei (void) +{ + common_test (TESTUDEVRULESDIR_HUAWEI); +} +#endif + +#if defined ENABLE_PLUGIN_MBM +static void +test_mbm (void) +{ + common_test (TESTUDEVRULESDIR_MBM); +} +#endif + +#if defined ENABLE_PLUGIN_NOKIA_ICERA +static void +test_nokia_icera (void) +{ + common_test (TESTUDEVRULESDIR_NOKIA_ICERA); +} +#endif + +#if defined ENABLE_PLUGIN_ZTE +static void +test_zte (void) +{ + common_test (TESTUDEVRULESDIR_ZTE); +} +#endif + +#if defined ENABLE_PLUGIN_LONGCHEER +static void +test_longcheer (void) +{ + common_test (TESTUDEVRULESDIR_LONGCHEER); +} +#endif + +#if defined ENABLE_PLUGIN_SIMTECH +static void +test_simtech (void) +{ + common_test (TESTUDEVRULESDIR_SIMTECH); +} +#endif + +#if defined ENABLE_PLUGIN_X22X +static void +test_x22x (void) +{ + common_test (TESTUDEVRULESDIR_X22X); +} +#endif + +#if defined ENABLE_PLUGIN_CINTERION +static void +test_cinterion (void) +{ + common_test (TESTUDEVRULESDIR_CINTERION); +} +#endif + +#if defined ENABLE_PLUGIN_DELL +static void +test_dell (void) +{ + common_test (TESTUDEVRULESDIR_DELL); +} +#endif + +#if defined ENABLE_PLUGIN_TELIT +static void +test_telit (void) +{ + common_test (TESTUDEVRULESDIR_TELIT); +} +#endif + +#if defined ENABLE_PLUGIN_MTK +static void +test_mtk (void) +{ + common_test (TESTUDEVRULESDIR_MTK); +} +#endif + +#if defined ENABLE_PLUGIN_HAIER +static void +test_haier (void) +{ + common_test (TESTUDEVRULESDIR_HAIER); +} +#endif + +#if defined ENABLE_PLUGIN_FIBOCOM +static void +test_fibocom (void) +{ + common_test (TESTUDEVRULESDIR_FIBOCOM); +} +#endif + +#if defined ENABLE_PLUGIN_QUECTEL +static void +test_quectel (void) +{ + common_test (TESTUDEVRULESDIR_QUECTEL); +} +#endif + +#if defined ENABLE_PLUGIN_GOSUNCN +static void +test_gosuncn (void) +{ + common_test (TESTUDEVRULESDIR_GOSUNCN); +} +#endif + +#if defined ENABLE_PLUGIN_QCOM_SOC && defined WITH_QMI +static void +test_qcom_soc (void) +{ + common_test (TESTUDEVRULESDIR_QCOM_SOC); +} +#endif + +#if defined ENABLE_PLUGIN_LINKTOP +static void +test_linktop (void) +{ + common_test (TESTUDEVRULESDIR_LINKTOP); +} +#endif + +/************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/MM/test-udev-rules/placeholder", test_placeholder); + +#if defined ENABLE_PLUGIN_HUAWEI + g_test_add_func ("/MM/test-udev-rules/huawei", test_huawei); +#endif +#if defined ENABLE_PLUGIN_MBM + g_test_add_func ("/MM/test-udev-rules/mbm", test_mbm); +#endif +#if defined ENABLE_PLUGIN_NOKIA_ICERA + g_test_add_func ("/MM/test-udev-rules/nokia-icera", test_nokia_icera); +#endif +#if defined ENABLE_PLUGIN_ZTE + g_test_add_func ("/MM/test-udev-rules/zte", test_zte); +#endif +#if defined ENABLE_PLUGIN_LONGCHEER + g_test_add_func ("/MM/test-udev-rules/longcheer", test_longcheer); +#endif +#if defined ENABLE_PLUGIN_SIMTECH + g_test_add_func ("/MM/test-udev-rules/simtech", test_simtech); +#endif +#if defined ENABLE_PLUGIN_X22X + g_test_add_func ("/MM/test-udev-rules/x22x", test_x22x); +#endif +#if defined ENABLE_PLUGIN_CINTERION + g_test_add_func ("/MM/test-udev-rules/cinterion", test_cinterion); +#endif +#if defined ENABLE_PLUGIN_DELL + g_test_add_func ("/MM/test-udev-rules/dell", test_dell); +#endif +#if defined ENABLE_PLUGIN_TELIT + g_test_add_func ("/MM/test-udev-rules/telit", test_telit); +#endif +#if defined ENABLE_PLUGIN_MTK + g_test_add_func ("/MM/test-udev-rules/mtk", test_mtk); +#endif +#if defined ENABLE_PLUGIN_HAIER + g_test_add_func ("/MM/test-udev-rules/haier", test_haier); +#endif +#if defined ENABLE_PLUGIN_FIBOCOM + g_test_add_func ("/MM/test-udev-rules/fibocom", test_fibocom); +#endif +#if defined ENABLE_PLUGIN_QUECTEL + g_test_add_func ("/MM/test-udev-rules/quectel", test_quectel); +#endif +#if defined ENABLE_PLUGIN_GOSUNCN + g_test_add_func ("/MM/test-udev-rules/gosuncn", test_gosuncn); +#endif +#if defined ENABLE_PLUGIN_QCOM_SOC && defined WITH_QMI + g_test_add_func ("/MM/test-udev-rules/qcom-soc", test_qcom_soc); +#endif +#if defined ENABLE_PLUGIN_LINKTOP + g_test_add_func ("/MM/test-udev-rules/linktop", test_linktop); +#endif + + return g_test_run (); +} diff --git a/src/plugins/thuraya/mm-broadband-modem-thuraya.c b/src/plugins/thuraya/mm-broadband-modem-thuraya.c new file mode 100644 index 00000000..92c8a8a2 --- /dev/null +++ b/src/plugins/thuraya/mm-broadband-modem-thuraya.c @@ -0,0 +1,284 @@ +/* -*- 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 - 2012 Ammonit Measurement GmbH + * Author: Aleksander Morgado <aleksander@lanedo.com> + * Copyright (C) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-errors-types.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-messaging.h" +#include "mm-broadband-modem-thuraya.h" +#include "mm-broadband-bearer.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-thuraya.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_messaging_init (MMIfaceModemMessaging *iface); + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemThuraya, mm_broadband_modem_thuraya, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init) ); + +/*****************************************************************************/ +/* Operator Code and Name loading (3GPP interface) */ + +static gchar * +load_operator_code_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_operator_code (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_return_pointer (task, g_strdup ("90106"), g_free); + g_object_unref (task); +} + +static gchar * +load_operator_name_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_operator_name (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_return_pointer (task, g_strdup ("THURAYA"), g_free); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Load supported modes (Modem inteface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + GArray *combinations; + MMModemModeCombination mode; + + task = g_task_new (self, NULL, callback, user_data); + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1); + + /* Report any, Thuraya connections are packet-switched */ + mode.allowed = MM_MODEM_MODE_ANY; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Load supported SMS storages (Messaging interface) */ + +typedef struct { + GArray *mem1; + GArray *mem2; + GArray *mem3; +} SupportedStoragesResult; + +static void +supported_storages_result_free (SupportedStoragesResult *result) +{ + if (result->mem1) + g_array_unref (result->mem1); + if (result->mem2) + g_array_unref (result->mem2); + if (result->mem3) + g_array_unref (result->mem3); + g_free (result); +} + +static gboolean +modem_messaging_load_supported_storages_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GArray **mem1, + GArray **mem2, + GArray **mem3, + GError **error) +{ + SupportedStoragesResult *result; + + result = g_task_propagate_pointer (G_TASK (res), error); + if (!result) + return FALSE; + + *mem1 = g_array_ref (result->mem1); + *mem2 = g_array_ref (result->mem2); + *mem3 = g_array_ref (result->mem3); + supported_storages_result_free (result); + return TRUE; +} + +static void +cpms_format_check_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + SupportedStoragesResult *result; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + result = g_new0 (SupportedStoragesResult, 1); + + /* Parse reply */ + if (!mm_thuraya_3gpp_parse_cpms_test_response (response, + &result->mem1, + &result->mem2, + &result->mem3, + &error)) { + supported_storages_result_free (result); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_task_return_pointer (task, result, (GDestroyNotify) supported_storages_result_free); + g_object_unref (task); +} + +static void +modem_messaging_load_supported_storages (MMIfaceModemMessaging *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Check support storages */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CPMS=?", + 3, + TRUE, + (GAsyncReadyCallback)cpms_format_check_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ + +MMBroadbandModemThuraya * +mm_broadband_modem_thuraya_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_THURAYA, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_thuraya_init (MMBroadbandModemThuraya *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + /* No need to power-up/power-down the modem */ + iface->load_power_state = NULL; + iface->load_power_state_finish = NULL; + iface->modem_power_up = NULL; + iface->modem_power_up_finish = NULL; + iface->modem_power_down = NULL; + iface->modem_power_down_finish = NULL; + + /* Supported modes cannot be queried */ + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + /* Fixed operator code and name to be reported */ + iface->load_operator_name = load_operator_name; + iface->load_operator_name_finish = load_operator_name_finish; + iface->load_operator_code = load_operator_code; + iface->load_operator_code_finish = load_operator_code_finish; + + /* Don't try to scan networks with AT+COPS=?. + * The Thuraya XT does not seem to properly support AT+COPS=?. + * When issuing this command, it seems to get sufficiently confused + * to drop the signal. Furthermore, it is useless anyway as there is only + * one network supported, Thuraya. + */ + iface->scan_networks = NULL; + iface->scan_networks_finish = NULL; +} + +static void +iface_modem_messaging_init (MMIfaceModemMessaging *iface) +{ + iface->load_supported_storages = modem_messaging_load_supported_storages; + iface->load_supported_storages_finish = modem_messaging_load_supported_storages_finish; +} + +static void +mm_broadband_modem_thuraya_class_init (MMBroadbandModemThurayaClass *klass) +{ +} diff --git a/src/plugins/thuraya/mm-broadband-modem-thuraya.h b/src/plugins/thuraya/mm-broadband-modem-thuraya.h new file mode 100644 index 00000000..42df9b9c --- /dev/null +++ b/src/plugins/thuraya/mm-broadband-modem-thuraya.h @@ -0,0 +1,50 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2011 Red Hat, Inc. + * Copyright (C) 2011 Google Inc. + * Copyright (C) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch> + */ + +#ifndef MM_BROADBAND_MODEM_THURAYA_H +#define MM_BROADBAND_MODEM_THURAYA_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_THURAYA (mm_broadband_modem_thuraya_get_type ()) +#define MM_BROADBAND_MODEM_THURAYA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_THURAYA, MMBroadbandModemThuraya)) +#define MM_BROADBAND_MODEM_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_THURAYA, MMBroadbandModemThurayaClass)) +#define MM_IS_BROADBAND_MODEM_THURAYA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_THURAYA)) +#define MM_IS_BROADBAND_MODEM_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_THURAYA)) +#define MM_BROADBAND_MODEM_THURAYA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_THURAYA, MMBroadbandModemThurayaClass)) + +typedef struct _MMBroadbandModemThuraya MMBroadbandModemThuraya; +typedef struct _MMBroadbandModemThurayaClass MMBroadbandModemThurayaClass; + +struct _MMBroadbandModemThuraya { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemThurayaClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_thuraya_get_type (void); + +MMBroadbandModemThuraya *mm_broadband_modem_thuraya_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_THURAYA_H */ diff --git a/src/plugins/thuraya/mm-modem-helpers-thuraya.c b/src/plugins/thuraya/mm-modem-helpers-thuraya.c new file mode 100644 index 00000000..0c713a18 --- /dev/null +++ b/src/plugins/thuraya/mm-modem-helpers-thuraya.c @@ -0,0 +1,148 @@ +/* -*- 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) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch> + * + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MMCLI +#include <libmm-glib.h> + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-thuraya.h" + +/*************************************************************************/ + +static MMSmsStorage +storage_from_str (const gchar *str) +{ + if (g_str_equal (str, "SM")) + return MM_SMS_STORAGE_SM; + if (g_str_equal (str, "ME")) + return MM_SMS_STORAGE_ME; + if (g_str_equal (str, "MT")) + return MM_SMS_STORAGE_MT; + if (g_str_equal (str, "SR")) + return MM_SMS_STORAGE_SR; + if (g_str_equal (str, "BM")) + return MM_SMS_STORAGE_BM; + if (g_str_equal (str, "TA")) + return MM_SMS_STORAGE_TA; + return MM_SMS_STORAGE_UNKNOWN; +} + +gboolean +mm_thuraya_3gpp_parse_cpms_test_response (const gchar *reply, + GArray **mem1, + GArray **mem2, + GArray **mem3, + GError **error) +{ +#define N_EXPECTED_GROUPS 3 + + gchar **splitp; + const gchar *splita[N_EXPECTED_GROUPS]; + guint i; + g_auto(GStrv) split = NULL; + g_autoptr(GRegex) r = NULL; + g_autoptr(GArray) tmp1 = NULL; + g_autoptr(GArray) tmp2 = NULL; + g_autoptr(GArray) tmp3 = NULL; + + g_assert (mem1 != NULL); + g_assert (mem2 != NULL); + g_assert (mem3 != NULL); + + split = g_strsplit (mm_strip_tag (reply, "+CPMS:"), " ", -1); + if (!split) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't split +CPMS response"); + return FALSE; + } + + /* remove empty strings, and count non-empty strings */ + i = 0; + for (splitp = split; *splitp; ++splitp) { + if (!**splitp) + continue; + if (i < N_EXPECTED_GROUPS) + splita[i] = *splitp; + ++i; + } + + if (i != N_EXPECTED_GROUPS) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse +CPMS response: invalid number of groups (%u != %u)", + i, N_EXPECTED_GROUPS); + return FALSE; + } + + r = g_regex_new ("\\s*\"([^,\\)]+)\"\\s*", 0, 0, NULL); + g_assert (r); + + for (i = 0; i < N_EXPECTED_GROUPS; i++) { + g_autoptr(GMatchInfo) match_info = NULL; + GArray *array; + + /* We always return a valid array, even if it may be empty */ + array = g_array_new (FALSE, FALSE, sizeof (MMSmsStorage)); + + /* Got a range group to match */ + if (g_regex_match (r, splita[i], 0, &match_info)) { + while (g_match_info_matches (match_info)) { + g_autofree gchar *str = NULL; + + str = g_match_info_fetch (match_info, 1); + if (str) { + MMSmsStorage storage; + + storage = storage_from_str (str); + g_array_append_val (array, storage); + } + + g_match_info_next (match_info, NULL); + } + } + + if (!tmp1) + tmp1 = array; + else if (!tmp2) + tmp2 = array; + else if (!tmp3) + tmp3 = array; + else + g_assert_not_reached (); + } + + /* Only return TRUE if all sets have been parsed correctly + * (even if the arrays may be empty) */ + if (tmp1 && tmp2 && tmp3) { + *mem1 = g_steal_pointer (&tmp1); + *mem2 = g_steal_pointer (&tmp2); + *mem3 = g_steal_pointer (&tmp3); + return TRUE; + } + + /* Otherwise, cleanup and return FALSE */ + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse +CPMS response: not all groups detected (mem1 %s, mem2 %s, mem3 %s)", + tmp1 ? "yes" : "no", + tmp2 ? "yes" : "no", + tmp3 ? "yes" : "no"); + return FALSE; +} diff --git a/src/plugins/thuraya/mm-modem-helpers-thuraya.h b/src/plugins/thuraya/mm-modem-helpers-thuraya.h new file mode 100644 index 00000000..33bb079f --- /dev/null +++ b/src/plugins/thuraya/mm-modem-helpers-thuraya.h @@ -0,0 +1,28 @@ +/* -*- 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) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch> + * + */ +#ifndef MM_MODEM_HELPERS_THURAYA_H +#define MM_MODEM_HELPERS_THURAYA_H + +#include <glib.h> + +/* AT+CPMS=? (Preferred SMS storage) response parser */ +gboolean mm_thuraya_3gpp_parse_cpms_test_response (const gchar *reply, + GArray **mem1, + GArray **mem2, + GArray **mem3, + GError **error); + +#endif /* MM_MODEM_HELPERS_THURAYA_H */ diff --git a/src/plugins/thuraya/mm-plugin-thuraya.c b/src/plugins/thuraya/mm-plugin-thuraya.c new file mode 100644 index 00000000..5097e24e --- /dev/null +++ b/src/plugins/thuraya/mm-plugin-thuraya.c @@ -0,0 +1,85 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH + * Author: Aleksander Morgado <aleksander@lanedo.com> + * Copyright (C) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-thuraya.h" +#include "mm-broadband-modem.h" +#include "mm-broadband-modem-thuraya.h" +#include "mm-private-boxed-types.h" + +G_DEFINE_TYPE (MMPluginThuraya, mm_plugin_thuraya, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_thuraya_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + static const guint16 vendor_ids[] = { 0x1a26, 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_THURAYA, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + NULL)); +} + +static void +mm_plugin_thuraya_init (MMPluginThuraya *self) +{ +} + +static void +mm_plugin_thuraya_class_init (MMPluginThurayaClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/thuraya/mm-plugin-thuraya.h b/src/plugins/thuraya/mm-plugin-thuraya.h new file mode 100644 index 00000000..fb86090d --- /dev/null +++ b/src/plugins/thuraya/mm-plugin-thuraya.h @@ -0,0 +1,48 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH + * Author: Aleksander Morgado <aleksander@lanedo.com> + * Copyright (C) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch> + */ + +#ifndef MM_PLUGIN_THURAYA_H +#define MM_PLUGIN_THURAYA_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_THURAYA (mm_plugin_thuraya_get_type ()) +#define MM_PLUGIN_THURAYA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_THURAYA, MMPluginThuraya)) +#define MM_PLUGIN_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_THURAYA, MMPluginThurayaClass)) +#define MM_IS_PLUGIN_THURAYA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_THURAYA)) +#define MM_IS_PLUGIN_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_THURAYA)) +#define MM_PLUGIN_THURAYA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_THURAYA, MMPluginThurayaClass)) + +typedef struct { + MMPlugin parent; +} MMPluginThuraya; + +typedef struct { + MMPluginClass parent; +} MMPluginThurayaClass; + +GType mm_plugin_thuraya_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_THURAYA_H */ diff --git a/src/plugins/thuraya/tests/test-mm-modem-helpers-thuraya.c b/src/plugins/thuraya/tests/test-mm-modem-helpers-thuraya.c new file mode 100644 index 00000000..bdc073d0 --- /dev/null +++ b/src/plugins/thuraya/tests/test-mm-modem-helpers-thuraya.c @@ -0,0 +1,106 @@ +/* -*- 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) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch> + * + */ +#include <stdio.h> +#include <glib.h> +#include <glib-object.h> +#include <locale.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-thuraya.h" +#include "mm-log-test.h" + +/*****************************************************************************/ +/* Test CPMS response */ + +static gboolean +is_storage_supported (GArray *supported, + MMSmsStorage storage) +{ + guint i; + + for (i = 0; i < supported->len; i++) { + if (storage == g_array_index (supported, MMSmsStorage, i)) + return TRUE; + } + + return FALSE; +} + +static void +test_cpms_response_thuraya (void *f, gpointer d) +{ + /* + * First: ("ME","MT") 2-item group + * Second: "ME" 1 item + * Third: ("SM") 1-item group + */ + const gchar *reply = "+CPMS: \"MT\",\"SM\",\"BM\",\"ME\",\"SR\", \"MT\",\"SM\",\"BM\",\"ME\",\"SR\", \"MT\",\"SM\",\"BM\",\"ME\",\"SR\" "; + GArray *mem1 = NULL; + GArray *mem2 = NULL; + GArray *mem3 = NULL; + GError *error = NULL; + + g_debug ("Testing thuraya +CPMS=? response..."); + + g_assert (mm_thuraya_3gpp_parse_cpms_test_response (reply, &mem1, &mem2, &mem3, &error)); + g_assert_no_error (error); + g_assert_cmpuint (mem1->len, ==, 5); + g_assert (is_storage_supported (mem1, MM_SMS_STORAGE_MT)); + g_assert (is_storage_supported (mem1, MM_SMS_STORAGE_SM)); + g_assert (is_storage_supported (mem1, MM_SMS_STORAGE_BM)); + g_assert (is_storage_supported (mem1, MM_SMS_STORAGE_ME)); + g_assert (is_storage_supported (mem1, MM_SMS_STORAGE_SR)); + g_assert_cmpuint (mem2->len, ==, 5); + g_assert (is_storage_supported (mem2, MM_SMS_STORAGE_MT)); + g_assert (is_storage_supported (mem2, MM_SMS_STORAGE_SM)); + g_assert (is_storage_supported (mem2, MM_SMS_STORAGE_BM)); + g_assert (is_storage_supported (mem2, MM_SMS_STORAGE_ME)); + g_assert (is_storage_supported (mem2, MM_SMS_STORAGE_SR)); + g_assert_cmpuint (mem3->len, ==, 5); + g_assert (is_storage_supported (mem3, MM_SMS_STORAGE_MT)); + g_assert (is_storage_supported (mem3, MM_SMS_STORAGE_SM)); + g_assert (is_storage_supported (mem3, MM_SMS_STORAGE_BM)); + g_assert (is_storage_supported (mem3, MM_SMS_STORAGE_ME)); + g_assert (is_storage_supported (mem3, MM_SMS_STORAGE_SR)); + + g_array_unref (mem1); + g_array_unref (mem2); + g_array_unref (mem3); +} + +/*****************************************************************************/ + +#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (GTestFixtureFunc) t, NULL) + +int main (int argc, char **argv) +{ + GTestSuite *suite; + gint result; + + g_test_init (&argc, &argv, NULL); + + suite = g_test_get_root (); + + g_test_suite_add (suite, TESTCASE (test_cpms_response_thuraya, NULL)); + + result = g_test_run (); + + return result; +} diff --git a/src/plugins/tplink/77-mm-tplink-port-types.rules b/src/plugins/tplink/77-mm-tplink-port-types.rules new file mode 100644 index 00000000..f5c8894e --- /dev/null +++ b/src/plugins/tplink/77-mm-tplink-port-types.rules @@ -0,0 +1,15 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_tplink_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2357", GOTO="mm_tplink_port_types" +GOTO="mm_tplink_port_types_end" + +LABEL="mm_tplink_port_types" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# D-Link DWM-222 +ATTRS{idVendor}=="2357", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="2357", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="2357", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +LABEL="mm_tplink_port_types_end" diff --git a/src/plugins/tplink/mm-plugin-tplink.c b/src/plugins/tplink/mm-plugin-tplink.c new file mode 100644 index 00000000..1c03fef0 --- /dev/null +++ b/src/plugins/tplink/mm-plugin-tplink.c @@ -0,0 +1,94 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-tplink.h" +#include "mm-broadband-modem.h" + +#if defined WITH_QMI +# include "mm-broadband-modem-qmi.h" +#endif + +G_DEFINE_TYPE (MMPluginTplink, mm_plugin_tplink, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered TP-Link modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x2357, 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_TPLINK, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + NULL)); +} + +static void +mm_plugin_tplink_init (MMPluginTplink *self) +{ +} + +static void +mm_plugin_tplink_class_init (MMPluginTplinkClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/tplink/mm-plugin-tplink.h b/src/plugins/tplink/mm-plugin-tplink.h new file mode 100644 index 00000000..16dc5f5b --- /dev/null +++ b/src/plugins/tplink/mm-plugin-tplink.h @@ -0,0 +1,40 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_TPLINK_H +#define MM_PLUGIN_TPLINK_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_TPLINK (mm_plugin_tplink_get_type ()) +#define MM_PLUGIN_TPLINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_TPLINK, MMPluginTplink)) +#define MM_PLUGIN_TPLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_TPLINK, MMPluginTplinkClass)) +#define MM_IS_PLUGIN_TPLINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_TPLINK)) +#define MM_IS_PLUGIN_TPLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_TPLINK)) +#define MM_PLUGIN_TPLINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_TPLINK, MMPluginTplinkClass)) + +typedef struct { + MMPlugin parent; +} MMPluginTplink; + +typedef struct { + MMPluginClass parent; +} MMPluginTplinkClass; + +GType mm_plugin_tplink_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_TPLINK_H */ diff --git a/src/plugins/ublox/77-mm-ublox-port-types.rules b/src/plugins/ublox/77-mm-ublox-port-types.rules new file mode 100644 index 00000000..c2a1ac99 --- /dev/null +++ b/src/plugins/ublox/77-mm-ublox-port-types.rules @@ -0,0 +1,78 @@ +# do not edit this file, it will be overwritten on update +ACTION!="add|change|move|bind", GOTO="mm_ublox_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1546", GOTO="mm_ublox_port_types" +GOTO="mm_ublox_port_types_end" + +LABEL="mm_ublox_port_types" + +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Fully ignore u-blox GPS devices +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a5", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a6", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a8", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a9", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Toby-L4 port types +# ttyACM0 (if #2): secondary (ignore) +# ttyACM1 (if #4): debug port (ignore) +# ttyACM2 (if #6): primary +# Wait up to 20s for the +READY URC +# ttyACM3 (if #8): AT port for FOTA (ignore) +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_UBLOX_PORT_READY_DELAY}="20" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1" + +# TOBY-L200 +# Wait up to 20s before probing AT ports +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1141", ENV{ID_MM_UBLOX_PORT_READY_DELAY}="20" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1143", ENV{ID_MM_UBLOX_PORT_READY_DELAY}="20" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1146", ENV{ID_MM_UBLOX_PORT_READY_DELAY}="20" + +# TOBY-R2 port types +# ttyACM0 (if #0): primary +# ttyACM1 (if #2): secondary +# ttyACM2 (if #4): tertiary +# ttyACM3 (if #6): GNSS Tunneling (ignore) +# ttyACM4 (if #8): SIM Access Profile (ignore) +# ttyACM5 (if #10): Primary Log for diagnostics (ignore) +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1107", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1107", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1107", ENV{.MM_USBIFNUM}=="0a", ENV{ID_MM_PORT_IGNORE}="1" + +# LARA-R2 port types +# ttyACM0 (if #0): primary +# ttyACM1 (if #2): secondary +# ttyACM2 (if #4): tertiary +# ttyACM3 (if #6): GNSS Tunneling (ignore) +# ttyACM4 (if #8): SIM Access Profile (ignore) +# ttyACM5 (if #10): Primary Log for diagnostics (ignore) +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="0a", ENV{ID_MM_PORT_IGNORE}="1" + +# LISA-U2 / SARA-U2 port types +# ttyACM0 (if #0): primary +# ttyACM1 (if #2): secondary +# ttyACM2 (if #4): tertiary +# ttyACM3 (if #6): GNSS Tunneling (ignore) +# ttyACM4 (if #8): Primary Log for diagnostics (ignore) +# ttyACM5 (if #10): Secondary Log for diagnostics (ignore) +# ttyACM6 (if #12): SAP (SIM Access Profile) (ignore) +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1102", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1102", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1102", ENV{.MM_USBIFNUM}=="0a", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1102", ENV{.MM_USBIFNUM}=="0c", ENV{ID_MM_PORT_IGNORE}="1" + +# LISA-U2 / SARA-U2 (alternative configuration) port types +# ttyACM0 (if #0): primary +# ttyACM1 (if #2): GNSS Tunneling (ignore) +# ttyACM2 (if #4): Primary Log for diagnostics (ignore) +# ttyACM3 (if #6): SAP (SIM Access Profile) (ignore) +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1104", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1104", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1104", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" + +LABEL="mm_ublox_port_types_end" diff --git a/src/plugins/ublox/README b/src/plugins/ublox/README new file mode 100644 index 00000000..573be2ce --- /dev/null +++ b/src/plugins/ublox/README @@ -0,0 +1,162 @@ + +The 'ublox' plugin is originally targeted for the u-blox TOBY-L2 series, +although it may be used with other kind of devices likely without many issues. + +===================================== + USB profiles and networking modes +===================================== + +The TOBY-L2 devices may work in multiple different USB profiles: + + * AT+UUSBCONF=0: fairly back-compatible profile, where only cdc-acm TTYs are + exposed. ModemManager will default to PPP for the connection setup when in + this profile. + + * AT+UUSBCONF=2: ECM profile, where multiple cdc-acm TTYs are exposed along + with a ECM network interface. + + * AT+UUSBCONF=3: RNDIS profile, where one cdc-acm TTY and a RNDIS network + interface are exposed. This is the default factory-programmed value. + +When a profile with a network interface (ECM or RNDIS) is in use, the device may +work in multiple networking modes: + + * AT+UBMCONF=1: Router mode, with a built-in DHCP server running behind the + network interface. The network interface will be assigned an IP address from + a subnet managed by the device itself. This is the default factory-programmed + value. E.g.: + + $ ip addr + 9: usb0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000 + link/ether 02:07:01:15:00:0b brd ff:ff:ff:ff:ff:ff + inet 192.168.1.100/24 brd 192.168.1.255 scope global dynamic usb0 + valid_lft 43009sec preferred_lft 43009sec + + $ ip route + default via 192.168.1.1 dev usb0 proto static metric 700 + 192.168.1.0/24 dev usb0 proto kernel scope link src 192.168.1.100 metric 700 + + * AT+UBMCONF=2: Bridge mode, where static IP addressing and routing must be + performed once connected. The network interface will be assigned the same IP + address provided by the network operator. The plugin uses 'AT+UIPADDR=N' the + default gateway IP settings and 'AT++CGCONTRDP=N' to retrieve the interface + IP settings and DNS setup. + + $ ip addr + 11: usb0: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000 + link/ether 02:07:01:15:00:0b brd ff:ff:ff:ff:ff:ff + inet 47.59.109.26/32 brd 47.59.109.26 scope global usb0 + valid_lft forever preferred_lft forever + + $ ip route + default via 47.59.109.229 dev usb0 proto static metric 700 + 47.59.109.26 dev usb0 proto kernel scope link src 47.59.109.26 metric 700 + 47.59.109.229 dev usb0 proto static scope link metric 700 + +The 'ublox' plugin in ModemManager works with any of the previous combinations +seamlessly. It is assumed that the device doesn't change either USB profile or +networking mode once it has been detected and processed by ModemManager. + +NOTE: If manually selecting different USB profiles or networking modes, remember +to reset the module before assuming the new settings have been applied. E.g., +using plain mmcli commands: + $ sudo mmcli -m 0 --command="AT+UUSBCONF=3" + $ sudo mmcli -m 0 --reset + +================================= + Connection setup +================================= + +The plugin allows to connect to specific APNs in the usual way (i.e. by creating +a PDP context for the specific APN), and then activating the PDP context with +'AT+CGACT=[CID]'. + +Authentication settings of the APN (user, password, authentication type) are +also supported via the 'AT+UAUTHREQ' command. + +The plugin doesn't currently support reporting as auto-connected the default LTE +bearer. + +======================================== + Connection monitoring and statistics +======================================== + +The status of the connection of the specific PDP context is monitored +periodically using 'AT+CGACT?', in order to detect network-originated +disconnections. This implementation is given in the Generic broadband bearer +implementation, and is not ublox-specific. + +If the device supports it, connection TX/RX statistics will also be periodically +loaded using the AT+UGCNTRD command. Note, though, that the TOBY-L2 doesn't seem +to support this information via control commands. + +=========================================== + Supported and current mode combinations +=========================================== + +The full list of supported mode combinations is loaded using 'AT+URAT=?', and +then filtered by device product name to remove technologies not supported in +several devices. E.g. the standard TOBY-L2 list of supported mode combinations +will include all 2G, 3G and 4G, but if the device is a L201, 2G support will be +removed from the list. + +The current mode combination in use is loaded using 'AT+URAT?', and the setting +may be changed using the 'AT+URAT=X' request. + +In order to be able to update this setting, the device will be put in low-power +mode ('AT+CFUN=4'), then the setting update will be run, and finally the device +will recover the previous functionality mode it was in (e.g. 'AT+CFUN=1' if it +was in full functionality mode). + +=============================== + Supported and current bands +=============================== + +The full list of supported bands is hardcoded based on the supported modes of the +device. There is no runtime loading of which are the supported bands because the +'AT+UBANDSEL=?' command gives different results depending on the current access +technology (i.e. there is no single full list of supported bands reported). + +The current list of bands is loaded via the 'AT+UBANDSEL?' command, and the +setting may be changed using the 'AT+UBANDSEL=X' request. + +In order to be able to update this setting, the device will be put in low-power +mode ('AT+CFUN=4'), then the setting update will be run, and finally the device +will recover the previous functionality mode it was in (e.g. 'AT+CFUN=1' if it +was in full functionality mode). + +====================== + Functionality mode +====================== + +The plugin implements a custom 'AT+CFUN?' response parser because it provides +multiple modes that may be treated as 'low-power' by ModemManager (e.g. mode +'0' is minimum functionality, mode '4' is airplane mode and mode '19' is +minimum functionality with SIM deactivated). + +The plugin implements power-on ('AT+CFUN=1'), power-down ('AT+CFUN=4'), reset +('AT+CFUN=16') and power-off ('AT+CPWROFF'). As usual, a reset will trigger a +power cycle of the device, and the power-off will render the modem unusable +until it's power cycled externally. + +==================================== + Network registration and quality +==================================== + +The LTE specific 'AT+CEREG' registration checks will be enabled by default if +the module supports LTE. Additionally, a custom 'CREG/CGREG/CEREG' state number +parser is provided to support u-blox specific reported states (e.g. state '6' +to indicate 'sms only registration' on the '+CREG' indications for the CS +network when on LTE). + +The plugin implements the extended Signal interface, providing RSSI, RSCP, Ec/Io +and RSRQ measurements obtained with the 'AT+CESQ' command. + +================== + PIN management +================== + +A custom method to load the PIN/PUK remaining attempts is implemented based on +the 'AT+UPINCNT' command. + +Have fun! diff --git a/src/plugins/ublox/mm-broadband-bearer-ublox.c b/src/plugins/ublox/mm-broadband-bearer-ublox.c new file mode 100644 index 00000000..27db9b11 --- /dev/null +++ b/src/plugins/ublox/mm-broadband-bearer-ublox.c @@ -0,0 +1,1035 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-bearer-ublox.h" +#include "mm-base-modem-at.h" +#include "mm-log-object.h" +#include "mm-ublox-enums-types.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-ublox.h" + +G_DEFINE_TYPE (MMBroadbandBearerUblox, mm_broadband_bearer_ublox, MM_TYPE_BROADBAND_BEARER) + +enum { + PROP_0, + PROP_USB_PROFILE, + PROP_NETWORKING_MODE, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +struct _MMBroadbandBearerUbloxPrivate { + MMUbloxUsbProfile profile; + MMUbloxNetworkingMode mode; + MMUbloxBearerAllowedAuth allowed_auths; + FeatureSupport statistics; + FeatureSupport cedata; +}; + +/*****************************************************************************/ +/* Common connection context and task */ + +typedef struct { + MMBroadbandModem *modem; + MMPortSerialAt *primary; + MMPort *data; + guint cid; + gboolean auth_required; + MMBearerIpConfig *ip_config; /* For IPv4 settings */ +} CommonConnectContext; + +static void +common_connect_context_free (CommonConnectContext *ctx) +{ + if (ctx->ip_config) + g_object_unref (ctx->ip_config); + if (ctx->data) + g_object_unref (ctx->data); + g_object_unref (ctx->modem); + g_object_unref (ctx->primary); + g_slice_free (CommonConnectContext, ctx); +} + +static GTask * +common_connect_task_new (MMBroadbandBearerUblox *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + guint cid, + MMPort *data, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CommonConnectContext *ctx; + GTask *task; + + ctx = g_slice_new0 (CommonConnectContext); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify) common_connect_context_free); + + /* We need a net data port */ + if (data) + ctx->data = g_object_ref (data); + else { + ctx->data = mm_base_modem_get_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET); + if (!ctx->data) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to launch connection"); + g_object_unref (task); + return NULL; + } + } + + return task; +} + +/*****************************************************************************/ +/* 3GPP IP config (sub-step of the 3GPP Connection sequence) */ + +static gboolean +get_ip_config_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + MMBearerIpConfig **ipv4_config, + MMBearerIpConfig **ipv6_config, + GError **error) +{ + MMBearerConnectResult *configs; + MMBearerIpConfig *ipv4; + + configs = g_task_propagate_pointer (G_TASK (res), error); + if (!configs) + return FALSE; + + /* Just IPv4 for now */ + ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs); + g_assert (ipv4); + if (ipv4_config) + *ipv4_config = g_object_ref (ipv4); + if (ipv6_config) + *ipv6_config = NULL; + mm_bearer_connect_result_unref (configs); + return TRUE; +} + +static void +complete_get_ip_config_3gpp (GTask *task) +{ + CommonConnectContext *ctx; + + ctx = g_task_get_task_data (task); + g_assert (mm_bearer_ip_config_get_method (ctx->ip_config) != MM_BEARER_IP_METHOD_UNKNOWN); + g_task_return_pointer (task, + mm_bearer_connect_result_new (ctx->data, ctx->ip_config, NULL), + (GDestroyNotify) mm_bearer_connect_result_unref); + g_object_unref (task); +} + +static void +cgcontrdp_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerUblox *self; + const gchar *response; + GError *error = NULL; + CommonConnectContext *ctx; + gchar *local_address = NULL; + gchar *subnet = NULL; + gchar *dns_addresses[3] = { NULL, NULL, NULL }; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response || !mm_3gpp_parse_cgcontrdp_response (response, + NULL, /* cid */ + NULL, /* bearer id */ + NULL, /* apn */ + &local_address, + &subnet, + NULL, /* gateway_address */ + &dns_addresses[0], + &dns_addresses[1], + &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "IPv4 address retrieved: %s", local_address); + mm_bearer_ip_config_set_address (ctx->ip_config, local_address); + mm_obj_dbg (self, "IPv4 subnet retrieved: %s", subnet); + mm_bearer_ip_config_set_prefix (ctx->ip_config, mm_netmask_to_cidr (subnet)); + if (dns_addresses[0]) + mm_obj_dbg (self, "primary DNS retrieved: %s", dns_addresses[0]); + if (dns_addresses[1]) + mm_obj_dbg (self, "secondary DNS retrieved: %s", dns_addresses[1]); + mm_bearer_ip_config_set_dns (ctx->ip_config, (const gchar **) dns_addresses); + + g_free (local_address); + g_free (subnet); + g_free (dns_addresses[0]); + g_free (dns_addresses[1]); + + mm_obj_dbg (self, "finished IP settings retrieval for PDP context #%u...", ctx->cid); + + complete_get_ip_config_3gpp (task); +} + +static void +uipaddr_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerUblox *self; + const gchar *response; + gchar *cmd; + GError *error = NULL; + CommonConnectContext *ctx; + gchar *gw_ipv4_address = NULL; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response || !mm_ublox_parse_uipaddr_response (response, + NULL, /* cid */ + NULL, /* if_name */ + &gw_ipv4_address, + NULL, /* ipv4_subnet */ + NULL, /* ipv6_global_address */ + NULL, /* ipv6_link_local_address */ + &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "IPv4 gateway address retrieved: %s", gw_ipv4_address); + mm_bearer_ip_config_set_gateway (ctx->ip_config, gw_ipv4_address); + g_free (gw_ipv4_address); + + cmd = g_strdup_printf ("+CGCONTRDP=%u", ctx->cid); + mm_obj_dbg (self, "gathering IP and DNS information for PDP context #%u...", ctx->cid); + mm_base_modem_at_command (MM_BASE_MODEM (modem), + cmd, + 10, + FALSE, + (GAsyncReadyCallback) cgcontrdp_ready, + task); + g_free (cmd); +} + +static void +get_ip_config_3gpp (MMBroadbandBearer *_self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + MMBearerIpFamily ip_family, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerUblox *self = MM_BROADBAND_BEARER_UBLOX (_self); + GTask *task; + CommonConnectContext *ctx; + + if (!(task = common_connect_task_new (MM_BROADBAND_BEARER_UBLOX (self), + MM_BROADBAND_MODEM (modem), + primary, + cid, + data, + NULL, + callback, + user_data))) + return; + + ctx = g_task_get_task_data (task); + ctx->ip_config = mm_bearer_ip_config_new (); + + /* If we're in BRIDGE mode, we need to ask for static IP addressing details: + * - AT+UIPADDR=[CID] will give us the default gateway address. + * - +CGCONTRDP?[CID] will give us the IP address, subnet and DNS addresses. + */ + if (self->priv->mode == MM_UBLOX_NETWORKING_MODE_BRIDGE) { + gchar *cmd; + + mm_bearer_ip_config_set_method (ctx->ip_config, MM_BEARER_IP_METHOD_STATIC); + + cmd = g_strdup_printf ("+UIPADDR=%u", cid); + mm_obj_dbg (self, "gathering gateway information for PDP context #%u...", cid); + mm_base_modem_at_command (MM_BASE_MODEM (modem), + cmd, + 10, + FALSE, + (GAsyncReadyCallback) uipaddr_ready, + task); + g_free (cmd); + return; + } + + /* If we're in ROUTER networking mode, we just need to request DHCP on the + * network interface. Early return with that result. */ + if (self->priv->mode == MM_UBLOX_NETWORKING_MODE_ROUTER) { + mm_bearer_ip_config_set_method (ctx->ip_config, MM_BEARER_IP_METHOD_DHCP); + complete_get_ip_config_3gpp (task); + return; + } + + g_assert_not_reached (); +} + +/*****************************************************************************/ +/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ + +static MMPort * +dial_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return MM_PORT (g_task_propagate_pointer (G_TASK (res), error)); +} + +static void +cedata_activate_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerUblox *self) +{ + const gchar *response; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response) { + mm_obj_warn (self, "ECM data connection attempt failed: %s", error->message); + mm_base_bearer_report_connection_status (MM_BASE_BEARER (self), + MM_BEARER_CONNECTION_STATUS_DISCONNECTED); + g_error_free (error); + } + /* we received a full bearer object reference */ + g_object_unref (self); +} + +static void +cgact_activate_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + CommonConnectContext *ctx; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response) + g_task_return_error (task, error); + else + g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); + g_object_unref (task); +} + +static void +activate_3gpp (GTask *task) +{ + MMBroadbandBearerUblox *self; + CommonConnectContext *ctx; + g_autofree gchar *cmd = NULL; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* SARA-U2xx and LISA-U20x only expose one CDC-ECM interface. Hence, + * the fixed 0 as the interface index here. When we see modems with + * multiple interfaces, this needs to be revisited. */ + if (self->priv->profile == MM_UBLOX_USB_PROFILE_ECM && self->priv->cedata == FEATURE_SUPPORTED) { + cmd = g_strdup_printf ("+UCEDATA=%u,0", ctx->cid); + mm_obj_dbg (self, "establishing ECM data connection for PDP context #%u...", ctx->cid); + mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem), + cmd, + MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + FALSE, + (GAsyncReadyCallback) cedata_activate_ready, + g_object_ref (self)); + + /* We'll mark the task done here since the modem expects the DHCP + discover packet while +UCEDATA runs. If the command fails, we'll + mark the bearer disconnected later in the callback. */ + g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); + g_object_unref (task); + return; + } + + cmd = g_strdup_printf ("+CGACT=1,%u", ctx->cid); + mm_obj_dbg (self, "activating PDP context #%u...", ctx->cid); + mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem), + cmd, + MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + FALSE, + (GAsyncReadyCallback) cgact_activate_ready, + task); +} + +static void +test_cedata_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerUblox *self; + const gchar *response; + + self = g_task_get_source_object (task); + + response = mm_base_modem_at_command_finish (modem, res, NULL); + if (response) + self->priv->cedata = FEATURE_SUPPORTED; + else + self->priv->cedata = FEATURE_UNSUPPORTED; + mm_obj_dbg (self, "+UCEDATA command%s available", + (self->priv->cedata == FEATURE_SUPPORTED) ? "" : " not"); + + activate_3gpp (task); +} + +static void +test_cedata (GTask *task) +{ + MMBroadbandBearerUblox *self; + CommonConnectContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* We don't need to test for +UCEDATA if we're not using CDC-ECM or if we + have tested before. Instead, we jump right to the activation. */ + if (self->priv->profile != MM_UBLOX_USB_PROFILE_ECM || self->priv->cedata != FEATURE_SUPPORT_UNKNOWN) { + activate_3gpp (task); + return; + } + + mm_obj_dbg (self, "checking availability of +UCEDATA command..."); + mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem), + "+UCEDATA=?", + 3, + TRUE, /* allow_cached */ + (GAsyncReadyCallback) test_cedata_ready, + task); +} + +static void +uauthreq_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response) { + CommonConnectContext *ctx; + + ctx = g_task_get_task_data (task); + /* If authentication required and the +UAUTHREQ failed, abort */ + if (ctx->auth_required) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + /* Otherwise, ignore */ + g_error_free (error); + } + + test_cedata (task); +} + +static void +authenticate_3gpp (GTask *task) +{ + MMBroadbandBearerUblox *self; + CommonConnectContext *ctx; + g_autofree gchar *cmd = NULL; + MMBearerAllowedAuth allowed_auth; + gint ublox_auth = -1; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + allowed_auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + + if (!ctx->auth_required) { + mm_obj_dbg (self, "not using authentication"); + ublox_auth = 0; + goto out; + } + + if (allowed_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN || allowed_auth == (MM_BEARER_ALLOWED_AUTH_PAP | MM_BEARER_ALLOWED_AUTH_CHAP)) { + mm_obj_dbg (self, "using automatic authentication method"); + if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_AUTO) + ublox_auth = 3; + else if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP) + ublox_auth = 2; + else if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_PAP) + ublox_auth = 1; + else if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_NONE) + ublox_auth = 0; + } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_PAP) { + mm_obj_dbg (self, "using PAP authentication method"); + ublox_auth = 1; + } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_CHAP) { + mm_obj_dbg (self, "using CHAP authentication method"); + ublox_auth = 2; + } + +out: + + if (ublox_auth < 0) { + g_autofree gchar *str = NULL; + + str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth); + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Cannot use any of the specified authentication methods (%s)", str); + g_object_unref (task); + return; + } + + if (ublox_auth > 0) { + const gchar *user; + const gchar *password; + g_autofree gchar *quoted_user = NULL; + g_autofree gchar *quoted_password = NULL; + + user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + + quoted_user = mm_port_serial_at_quote_string (user); + quoted_password = mm_port_serial_at_quote_string (password); + + cmd = g_strdup_printf ("+UAUTHREQ=%u,%u,%s,%s", + ctx->cid, + ublox_auth, + quoted_user, + quoted_password); + } else + cmd = g_strdup_printf ("+UAUTHREQ=%u,0,\"\",\"\"", ctx->cid); + + mm_obj_dbg (self, "setting up authentication preferences in PDP context #%u...", ctx->cid); + mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem), + cmd, + 10, + FALSE, + (GAsyncReadyCallback) uauthreq_ready, + task); +} + +static void +uauthreq_test_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerUblox *self; + const gchar *response; + GError *error = NULL; + + self = g_task_get_source_object (task); + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response) + goto out; + + self->priv->allowed_auths = mm_ublox_parse_uauthreq_test (response, self, &error); +out: + if (error) { + CommonConnectContext *ctx; + + ctx = g_task_get_task_data (task); + /* If authentication required and the +UAUTHREQ test failed, abort */ + if (ctx->auth_required) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + /* Otherwise, ignore and jump to test_cedata directly as no auth setup + * is needed */ + g_error_free (error); + test_cedata (task); + return; + } + + authenticate_3gpp (task); +} + +static void +check_supported_authentication_methods (GTask *task) +{ + MMBroadbandBearerUblox *self; + CommonConnectContext *ctx; + const gchar *user; + const gchar *password; + MMBearerAllowedAuth allowed_auth; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + allowed_auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + + /* Flag whether authentication is required. If it isn't, we won't fail + * connection attempt if the +UAUTHREQ command fails */ + ctx->auth_required = (user && password && allowed_auth != MM_BEARER_ALLOWED_AUTH_NONE); + + /* If we already cached the support, not do it again */ + if (self->priv->allowed_auths != MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN) { + authenticate_3gpp (task); + return; + } + + mm_obj_dbg (self, "checking supported authentication methods..."); + mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem), + "+UAUTHREQ=?", + 10, + TRUE, /* allow cached */ + (GAsyncReadyCallback) uauthreq_test_ready, + task); +} + +static void +dial_3gpp (MMBroadbandBearer *self, + MMBaseModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + if (!(task = common_connect_task_new (MM_BROADBAND_BEARER_UBLOX (self), + MM_BROADBAND_MODEM (modem), + primary, + cid, + NULL, /* data, unused */ + cancellable, + callback, + user_data))) + return; + + check_supported_authentication_methods (task); +} + +/*****************************************************************************/ +/* 3GPP disconnection */ + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cgact_deactivate_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerUblox *self; + const gchar *response; + GError *error = NULL; + + self = g_task_get_source_object (task); + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response) { + /* TOBY-L4 and TOBY-L2 L2 don't allow to disconnect the last LTE bearer + * as that would imply de-registration from the LTE network, so we just + * assume that it's disconnected from the user point of view. + * + * TOBY-L4 reports this as a generic unknown Packet Domain Error, which + * is a bit unfortunate: + * AT+CGACT=0,1 + * +CME ERROR: 148 + * + * TOBY-L2 reports this as "LAST PDN disconnection not allowed" but using + * the legacy numeric value before 3GPP Rel 11 (i.e. 151 instead of 171). + * AT+CGACT=0,1 + * +CME ERROR: 151 + */ + if (!g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_GPRS_UNKNOWN) && + !g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_LAST_PDN_DISCONNECTION_NOT_ALLOWED) && + !g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_LAST_PDN_DISCONNECTION_NOT_ALLOWED_LEGACY) && + !g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "ignored error when disconnecting last LTE bearer: %s", error->message); + g_clear_error (&error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +disconnect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + g_autofree gchar *cmd = NULL; + + if (!(task = common_connect_task_new (MM_BROADBAND_BEARER_UBLOX (self), + MM_BROADBAND_MODEM (modem), + primary, + cid, + data, + NULL, + callback, + user_data))) + return; + + cmd = g_strdup_printf ("+CGACT=0,%u", cid); + mm_obj_dbg (self, "deactivating PDP context #%u...", cid); + mm_base_modem_at_command (MM_BASE_MODEM (modem), + cmd, + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, + FALSE, + (GAsyncReadyCallback) cgact_deactivate_ready, + task); +} + +/*****************************************************************************/ +/* Reload statistics */ + +typedef struct { + guint64 bytes_rx; + guint64 bytes_tx; +} StatsResult; + +static gboolean +reload_stats_finish (MMBaseBearer *self, + guint64 *bytes_rx, + guint64 *bytes_tx, + GAsyncResult *res, + GError **error) +{ + StatsResult *result; + + result = g_task_propagate_pointer (G_TASK (res), error); + if (!result) + return FALSE; + + if (bytes_rx) + *bytes_rx = result->bytes_rx; + if (bytes_tx) + *bytes_tx = result->bytes_tx; + g_free (result); + return TRUE; +} + +static void +ugcntrd_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerUblox *self; + const gchar *response; + GError *error = NULL; + guint64 tx_bytes = 0; + guint64 rx_bytes = 0; + gint cid; + + self = MM_BROADBAND_BEARER_UBLOX (g_task_get_source_object (task)); + + cid = mm_base_bearer_get_profile_id (MM_BASE_BEARER (self)); + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (response) { + if (cid == MM_3GPP_PROFILE_ID_UNKNOWN) + error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown profile id"); + else + mm_ublox_parse_ugcntrd_response_for_cid (response, + cid, + &tx_bytes, &rx_bytes, + NULL, NULL, + &error); + } + + if (error) { + g_prefix_error (&error, "Couldn't load PDP context %u statistics: ", cid); + g_task_return_error (task, error); + } else { + StatsResult *result; + + result = g_new (StatsResult, 1); + result->bytes_rx = rx_bytes; + result->bytes_tx = tx_bytes; + g_task_return_pointer (task, result, g_free); + } + g_object_unref (task); +} + +static void +run_reload_stats (MMBroadbandBearerUblox *self, + GTask *task) +{ + /* Unsupported? */ + if (self->priv->statistics == FEATURE_UNSUPPORTED) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Loading statistics isn't supported by this device"); + g_object_unref (task); + return; + } + + /* Supported */ + if (self->priv->statistics == FEATURE_SUPPORTED) { + MMBaseModem *modem = NULL; + + g_object_get (MM_BASE_BEARER (self), + MM_BASE_BEARER_MODEM, &modem, + NULL); + mm_base_modem_at_command (MM_BASE_MODEM (modem), + "+UGCNTRD", + 3, + FALSE, + (GAsyncReadyCallback) ugcntrd_ready, + task); + g_object_unref (modem); + return; + } + + g_assert_not_reached (); +} + +static void +ugcntrd_test_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerUblox *self; + + self = MM_BROADBAND_BEARER_UBLOX (g_task_get_source_object (task)); + + if (!mm_base_modem_at_command_finish (modem, res, NULL)) + self->priv->statistics = FEATURE_UNSUPPORTED; + else + self->priv->statistics = FEATURE_SUPPORTED; + + run_reload_stats (self, task); +} + +static void +reload_stats (MMBaseBearer *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + if (MM_BROADBAND_BEARER_UBLOX (self)->priv->statistics == FEATURE_SUPPORT_UNKNOWN) { + MMBaseModem *modem = NULL; + + g_object_get (MM_BASE_BEARER (self), + MM_BASE_BEARER_MODEM, &modem, + NULL); + + mm_base_modem_at_command (MM_BASE_MODEM (modem), + "+UGCNTRD=?", + 3, + FALSE, + (GAsyncReadyCallback) ugcntrd_test_ready, + task); + g_object_unref (modem); + return; + } + + run_reload_stats (MM_BROADBAND_BEARER_UBLOX (self), task); +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_broadband_bearer_ublox_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *bearer; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_ublox_new (MMBroadbandModem *modem, + MMUbloxUsbProfile profile, + MMUbloxNetworkingMode mode, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_assert (mode == MM_UBLOX_NETWORKING_MODE_ROUTER || mode == MM_UBLOX_NETWORKING_MODE_BRIDGE); + + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_UBLOX, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + MM_BROADBAND_BEARER_UBLOX_USB_PROFILE, profile, + MM_BROADBAND_BEARER_UBLOX_NETWORKING_MODE, mode, + NULL); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MMBroadbandBearerUblox *self = MM_BROADBAND_BEARER_UBLOX (object); + + switch (prop_id) { + case PROP_USB_PROFILE: + self->priv->profile = g_value_get_enum (value); + break; + case PROP_NETWORKING_MODE: + self->priv->mode = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MMBroadbandBearerUblox *self = MM_BROADBAND_BEARER_UBLOX (object); + + switch (prop_id) { + case PROP_USB_PROFILE: + g_value_set_enum (value, self->priv->profile); + break; + case PROP_NETWORKING_MODE: + g_value_set_enum (value, self->priv->mode); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +mm_broadband_bearer_ublox_init (MMBroadbandBearerUblox *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_BEARER_UBLOX, + MMBroadbandBearerUbloxPrivate); + + /* Defaults */ + self->priv->profile = MM_UBLOX_USB_PROFILE_UNKNOWN; + self->priv->mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN; + self->priv->allowed_auths = MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN; + self->priv->statistics = FEATURE_SUPPORT_UNKNOWN; + self->priv->cedata = FEATURE_SUPPORT_UNKNOWN; +} + +static void +mm_broadband_bearer_ublox_class_init (MMBroadbandBearerUbloxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandBearerUbloxPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + /* Note: the ublox plugin uses the generic AT+CGACT? based check to monitor + * the connection status (i.e. default load_connection_status()) */ + base_bearer_class->reload_stats = reload_stats; + base_bearer_class->reload_stats_finish = reload_stats_finish; + + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; + broadband_bearer_class->dial_3gpp = dial_3gpp; + broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; + broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp; + broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish; + + properties[PROP_USB_PROFILE] = + g_param_spec_enum (MM_BROADBAND_BEARER_UBLOX_USB_PROFILE, + "USB profile", + "USB profile in use", + MM_TYPE_UBLOX_USB_PROFILE, + MM_UBLOX_USB_PROFILE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_USB_PROFILE, properties[PROP_USB_PROFILE]); + + properties[PROP_NETWORKING_MODE] = + g_param_spec_enum (MM_BROADBAND_BEARER_UBLOX_NETWORKING_MODE, + "Networking mode", + "Networking mode in use", + MM_TYPE_UBLOX_NETWORKING_MODE, + MM_UBLOX_NETWORKING_MODE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_NETWORKING_MODE, properties[PROP_NETWORKING_MODE]); +} diff --git a/src/plugins/ublox/mm-broadband-bearer-ublox.h b/src/plugins/ublox/mm-broadband-bearer-ublox.h new file mode 100644 index 00000000..36c26894 --- /dev/null +++ b/src/plugins/ublox/mm-broadband-bearer-ublox.h @@ -0,0 +1,63 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_BEARER_UBLOX_H +#define MM_BROADBAND_BEARER_UBLOX_H + +#include <glib.h> +#include <glib-object.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-bearer.h" +#include "mm-modem-helpers-ublox.h" + +#define MM_TYPE_BROADBAND_BEARER_UBLOX (mm_broadband_bearer_ublox_get_type ()) +#define MM_BROADBAND_BEARER_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_UBLOX, MMBroadbandBearerUblox)) +#define MM_BROADBAND_BEARER_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_UBLOX, MMBroadbandBearerUbloxClass)) +#define MM_IS_BROADBAND_BEARER_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_UBLOX)) +#define MM_IS_BROADBAND_BEARER_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_UBLOX)) +#define MM_BROADBAND_BEARER_UBLOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_UBLOX, MMBroadbandBearerUbloxClass)) + +#define MM_BROADBAND_BEARER_UBLOX_USB_PROFILE "broadband-bearer-ublox-usb-profile" +#define MM_BROADBAND_BEARER_UBLOX_NETWORKING_MODE "broadband-bearer-ublox-networking-mode" + +typedef struct _MMBroadbandBearerUblox MMBroadbandBearerUblox; +typedef struct _MMBroadbandBearerUbloxClass MMBroadbandBearerUbloxClass; +typedef struct _MMBroadbandBearerUbloxPrivate MMBroadbandBearerUbloxPrivate; + +struct _MMBroadbandBearerUblox { + MMBroadbandBearer parent; + MMBroadbandBearerUbloxPrivate *priv; +}; + +struct _MMBroadbandBearerUbloxClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_ublox_get_type (void); + +void mm_broadband_bearer_ublox_new (MMBroadbandModem *modem, + MMUbloxUsbProfile profile, + MMUbloxNetworkingMode mode, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_ublox_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_BROADBAND_BEARER_UBLOX_H */ diff --git a/src/plugins/ublox/mm-broadband-modem-ublox.c b/src/plugins/ublox/mm-broadband-modem-ublox.c new file mode 100644 index 00000000..12be08c3 --- /dev/null +++ b/src/plugins/ublox/mm-broadband-modem-ublox.c @@ -0,0 +1,2093 @@ +/* -*- 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) 2016-2019 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-voice.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-ublox.h" +#include "mm-broadband-bearer-ublox.h" +#include "mm-sim-ublox.h" +#include "mm-modem-helpers-ublox.h" +#include "mm-ublox-enums-types.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_voice_init (MMIfaceModemVoice *iface); + +static MMIfaceModemVoice *iface_modem_voice_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemUblox, mm_broadband_modem_ublox, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)) + + +struct _MMBroadbandModemUbloxPrivate { + /* USB profile in use */ + MMUbloxUsbProfile profile; + gboolean profile_checked; + /* Networking mode in use */ + MMUbloxNetworkingMode mode; + gboolean mode_checked; + + /* Flag to specify whether a power operation is ongoing */ + gboolean power_operation_ongoing; + + /* Mode combination to apply if "any" requested */ + MMModemMode any_allowed; + + /* AT command configuration */ + UbloxSupportConfig support_config; + + /* Voice +UCALLSTAT support */ + GRegex *ucallstat_regex; + + FeatureSupport udtmfd_support; + GRegex *udtmfd_regex; + + /* Regex to ignore */ + GRegex *pbready_regex; +}; + +/*****************************************************************************/ +/* Per-model configuration loading */ + +static void +preload_support_config (MMBroadbandModemUblox *self) +{ + const gchar *model; + GError *error = NULL; + + /* Make sure we load only once */ + if (self->priv->support_config.loaded) + return; + + model = mm_iface_modem_get_model (MM_IFACE_MODEM (self)); + + if (!mm_ublox_get_support_config (model, &self->priv->support_config, &error)) { + mm_obj_warn (self, "loading support configuration failed: %s", error->message); + g_error_free (error); + + /* default to NOT SUPPORTED if unknown model */ + self->priv->support_config.method = SETTINGS_UPDATE_METHOD_UNKNOWN; + self->priv->support_config.uact = FEATURE_UNSUPPORTED; + self->priv->support_config.ubandsel = FEATURE_UNSUPPORTED; + } else + mm_obj_dbg (self, "support configuration found for '%s'", model); + + switch (self->priv->support_config.method) { + case SETTINGS_UPDATE_METHOD_CFUN: + mm_obj_dbg (self, " band update requires low-power mode"); + break; + case SETTINGS_UPDATE_METHOD_COPS: + mm_obj_dbg (self, " band update requires explicit unregistration"); + break; + case SETTINGS_UPDATE_METHOD_UNKNOWN: + /* not an error, this just means we don't need anything special */ + break; + default: + g_assert_not_reached (); + } + + switch (self->priv->support_config.uact) { + case FEATURE_SUPPORTED: + mm_obj_dbg (self, " UACT based band configuration supported"); + break; + case FEATURE_UNSUPPORTED: + mm_obj_dbg (self, " UACT based band configuration unsupported"); + break; + case FEATURE_SUPPORT_UNKNOWN: + default: + g_assert_not_reached(); + } + + switch (self->priv->support_config.ubandsel) { + case FEATURE_SUPPORTED: + mm_obj_dbg (self, " UBANDSEL based band configuration supported"); + break; + case FEATURE_UNSUPPORTED: + mm_obj_dbg (self, " UBANDSEL based band configuration unsupported"); + break; + case FEATURE_SUPPORT_UNKNOWN: + default: + g_assert_not_reached(); + } +} + +/*****************************************************************************/ + +static gboolean +acquire_power_operation (MMBroadbandModemUblox *self, + GError **error) +{ + if (self->priv->power_operation_ongoing) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_RETRY, + "An operation which requires power updates is currently in progress"); + return FALSE; + } + self->priv->power_operation_ongoing = TRUE; + return TRUE; +} + +static void +release_power_operation (MMBroadbandModemUblox *self) +{ + g_assert (self->priv->power_operation_ongoing); + self->priv->power_operation_ongoing = FALSE; +} + +/*****************************************************************************/ +/* Load supported bands (Modem interface) */ + +static GArray * +load_supported_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return (GArray *) g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_supported_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + GError *error = NULL; + GArray *bands = NULL; + const gchar *model; + + model = mm_iface_modem_get_model (self); + task = g_task_new (self, NULL, callback, user_data); + + bands = mm_ublox_get_supported_bands (model, 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); +} + +/*****************************************************************************/ +/* Load current bands (Modem interface) */ + +static GArray * +load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return (GArray *) g_task_propagate_pointer (G_TASK (res), error); +} + +static void +uact_load_current_bands_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + GArray *out; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + out = mm_ublox_parse_uact_response (response, &error); + if (!out) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_task_return_pointer (task, out, (GDestroyNotify)g_array_unref); + g_object_unref (task); +} + +static void +ubandsel_load_current_bands_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + const gchar *model; + GArray *out; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + model = mm_iface_modem_get_model (MM_IFACE_MODEM (self)); + out = mm_ublox_parse_ubandsel_response (response, model, self, &error); + if (!out) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_task_return_pointer (task, out, (GDestroyNotify)g_array_unref); + g_object_unref (task); +} + +static void +load_current_bands (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self); + GTask *task; + + preload_support_config (self); + + task = g_task_new (self, NULL, callback, user_data); + + if (self->priv->support_config.ubandsel == FEATURE_SUPPORTED) { + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+UBANDSEL?", + 3, + FALSE, + (GAsyncReadyCallback)ubandsel_load_current_bands_ready, + task); + return; + } + + if (self->priv->support_config.uact == FEATURE_SUPPORTED) { + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+UACT?", + 3, + FALSE, + (GAsyncReadyCallback)uact_load_current_bands_ready, + task); + return; + } + + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "loading current bands is unsupported"); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Set allowed modes/bands (Modem interface) */ + +typedef enum { + SET_CURRENT_MODES_BANDS_STEP_FIRST, + SET_CURRENT_MODES_BANDS_STEP_ACQUIRE, + SET_CURRENT_MODES_BANDS_STEP_CURRENT_POWER, + SET_CURRENT_MODES_BANDS_STEP_BEFORE_COMMAND, + SET_CURRENT_MODES_BANDS_STEP_COMMAND, + SET_CURRENT_MODES_BANDS_STEP_AFTER_COMMAND, + SET_CURRENT_MODES_BANDS_STEP_RELEASE, + SET_CURRENT_MODES_BANDS_STEP_LAST, +} SetCurrentModesBandsStep; + +typedef struct { + SetCurrentModesBandsStep step; + gchar *command; + MMModemPowerState initial_state; + GError *saved_error; +} SetCurrentModesBandsContext; + +static void +set_current_modes_bands_context_free (SetCurrentModesBandsContext *ctx) +{ + g_assert (!ctx->saved_error); + g_free (ctx->command); + g_slice_free (SetCurrentModesBandsContext, ctx); +} + +static void +set_current_modes_bands_context_new (GTask *task, + gchar *command) +{ + SetCurrentModesBandsContext *ctx; + + ctx = g_slice_new0 (SetCurrentModesBandsContext); + ctx->command = command; + ctx->initial_state = MM_MODEM_POWER_STATE_UNKNOWN; + ctx->step = SET_CURRENT_MODES_BANDS_STEP_FIRST; + g_task_set_task_data (task, ctx, (GDestroyNotify) set_current_modes_bands_context_free); +} + +static gboolean +common_set_current_modes_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void set_current_modes_bands_step (GTask *task); + +static void +set_current_modes_bands_reregister_in_network_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + SetCurrentModesBandsContext *ctx; + + ctx = g_task_get_task_data (task); + + /* propagate the error if none already set */ + mm_iface_modem_3gpp_reregister_in_network_finish (self, res, ctx->saved_error ? NULL : &ctx->saved_error); + + /* Go to next step (release power operation) regardless of the result */ + ctx->step++; + set_current_modes_bands_step (task); +} + +static void +set_current_modes_bands_after_command_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + SetCurrentModesBandsContext *ctx; + + ctx = g_task_get_task_data (task); + + /* propagate the error if none already set */ + mm_base_modem_at_command_finish (self, res, ctx->saved_error ? NULL : &ctx->saved_error); + + /* Go to next step (release power operation) regardless of the result */ + ctx->step++; + set_current_modes_bands_step (task); +} + +static void +set_current_modes_bands_command_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + SetCurrentModesBandsContext *ctx; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &ctx->saved_error)) + ctx->step = SET_CURRENT_MODES_BANDS_STEP_RELEASE; + else + ctx->step++; + + set_current_modes_bands_step (task); +} + +static void +set_current_modes_bands_before_command_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + SetCurrentModesBandsContext *ctx; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &ctx->saved_error)) + ctx->step = SET_CURRENT_MODES_BANDS_STEP_RELEASE; + else + ctx->step++; + + set_current_modes_bands_step (task); +} + +static void +set_current_modes_bands_current_power_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self); + SetCurrentModesBandsContext *ctx; + const gchar *response; + + ctx = g_task_get_task_data (task); + + g_assert (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN); + + response = mm_base_modem_at_command_finish (_self, res, &ctx->saved_error); + if (!response || !mm_ublox_parse_cfun_response (response, &ctx->initial_state, &ctx->saved_error)) + ctx->step = SET_CURRENT_MODES_BANDS_STEP_RELEASE; + else + ctx->step++; + + set_current_modes_bands_step (task); +} + +static void +set_current_modes_bands_step (GTask *task) +{ + MMBroadbandModemUblox *self; + SetCurrentModesBandsContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case SET_CURRENT_MODES_BANDS_STEP_FIRST: + ctx->step++; + /* fall through */ + + case SET_CURRENT_MODES_BANDS_STEP_ACQUIRE: + mm_obj_dbg (self, "acquiring power operation..."); + if (!acquire_power_operation (self, &ctx->saved_error)) { + ctx->step = SET_CURRENT_MODES_BANDS_STEP_LAST; + set_current_modes_bands_step (task); + return; + } + ctx->step++; + /* fall through */ + + case SET_CURRENT_MODES_BANDS_STEP_CURRENT_POWER: + /* If using CFUN, we check whether we're already in low-power mode. + * And if we are, we just skip triggering low-power mode ourselves. + */ + if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN) { + mm_obj_dbg (self, "checking current power operation..."); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN?", + 3, + FALSE, + (GAsyncReadyCallback) set_current_modes_bands_current_power_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case SET_CURRENT_MODES_BANDS_STEP_BEFORE_COMMAND: + /* If COPS required around the set command, run it unconditionally */ + if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_COPS) { + mm_obj_dbg (self, "deregistering from the network for configuration change..."); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+COPS=2", + 10, + FALSE, + (GAsyncReadyCallback) set_current_modes_bands_before_command_ready, + task); + return; + } + /* If CFUN required, check initial state before triggering low-power mode ourselves */ + else if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN) { + /* Do nothing if already in low-power mode */ + if (ctx->initial_state != MM_MODEM_POWER_STATE_LOW) { + mm_obj_dbg (self, "powering down for configuration change..."); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CFUN=4", + 3, + FALSE, + (GAsyncReadyCallback) set_current_modes_bands_before_command_ready, + task); + return; + } + } + + ctx->step++; + /* fall through */ + + case SET_CURRENT_MODES_BANDS_STEP_COMMAND: + mm_obj_dbg (self, "updating configuration..."); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + ctx->command, + 3, + FALSE, + (GAsyncReadyCallback) set_current_modes_bands_command_ready, + task); + return; + + case SET_CURRENT_MODES_BANDS_STEP_AFTER_COMMAND: + /* If COPS required around the set command, run it unconditionally */ + if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_COPS) { + mm_iface_modem_3gpp_reregister_in_network (MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback) set_current_modes_bands_reregister_in_network_ready, + task); + return; + } + /* If CFUN required, see if we need to recover power */ + else if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN) { + /* If we were in low-power mode before the change, do nothing, otherwise, + * full power mode back */ + if (ctx->initial_state != MM_MODEM_POWER_STATE_LOW) { + mm_obj_dbg (self, "recovering power state after configuration change..."); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CFUN=1", + 3, + FALSE, + (GAsyncReadyCallback) set_current_modes_bands_after_command_ready, + task); + return; + } + } + ctx->step++; + /* fall through */ + + case SET_CURRENT_MODES_BANDS_STEP_RELEASE: + mm_obj_dbg (self, "releasing power operation..."); + release_power_operation (self); + ctx->step++; + /* fall through */ + + case SET_CURRENT_MODES_BANDS_STEP_LAST: + if (ctx->saved_error) { + g_task_return_error (task, ctx->saved_error); + ctx->saved_error = NULL; + } else + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *command; + GError *error = NULL; + + preload_support_config (MM_BROADBAND_MODEM_UBLOX (self)); + + task = g_task_new (self, NULL, callback, user_data); + + /* Handle ANY */ + if (allowed == MM_MODEM_MODE_ANY) + allowed = MM_BROADBAND_MODEM_UBLOX (self)->priv->any_allowed; + + /* Build command */ + command = mm_ublox_build_urat_set_command (allowed, preferred, &error); + if (!command) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + set_current_modes_bands_context_new (task, command); + set_current_modes_bands_step (task); +} + +static void +set_current_bands (MMIfaceModem *_self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self); + GTask *task; + GError *error = NULL; + gchar *command = NULL; + const gchar *model; + + preload_support_config (self); + + task = g_task_new (self, NULL, callback, user_data); + + model = mm_iface_modem_get_model (_self); + + /* Build command */ + if (self->priv->support_config.uact == FEATURE_SUPPORTED) + command = mm_ublox_build_uact_set_command (bands_array, &error); + else if (self->priv->support_config.ubandsel == FEATURE_SUPPORTED) + command = mm_ublox_build_ubandsel_set_command (bands_array, model, &error); + + if (!command) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + set_current_modes_bands_context_new (task, command); + set_current_modes_bands_step (task); +} + +/*****************************************************************************/ +/* Load current modes (Modem interface) */ + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + const gchar *response; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + return mm_ublox_parse_urat_read_response (response, self, allowed, preferred, error); +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+URAT?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + const gchar *response; + GArray *combinations; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + if (!(combinations = mm_ublox_parse_urat_test_response (response, self, error))) + return FALSE; + + if (!(combinations = mm_ublox_filter_supported_modes (mm_iface_modem_get_model (self), combinations, self, error))) + return FALSE; + + /* Decide and store which combination to apply when ANY requested */ + MM_BROADBAND_MODEM_UBLOX (self)->priv->any_allowed = mm_ublox_get_modem_mode_any (combinations); + + /* If 4G supported, explicitly use +CEREG */ + if (MM_BROADBAND_MODEM_UBLOX (self)->priv->any_allowed & MM_MODEM_MODE_4G) + g_object_set (self, MM_IFACE_MODEM_3GPP_EPS_NETWORK_SUPPORTED, TRUE, NULL); + + return combinations; +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+URAT=?", + 3, + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Power state loading (Modem interface) */ + +static MMModemPowerState +load_power_state_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + MMModemPowerState state = MM_MODEM_POWER_STATE_UNKNOWN; + const gchar *response; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (response) + mm_ublox_parse_cfun_response (response, &state, error); + return state; +} + +static void +load_power_state (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Modem power up/down/off (Modem interface) */ + +static gboolean +common_modem_power_operation_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +power_operation_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + release_power_operation (MM_BROADBAND_MODEM_UBLOX (self)); + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +common_modem_power_operation (MMBroadbandModemUblox *self, + const gchar *command, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + GError *error = NULL; + + task = g_task_new (self, NULL, callback, user_data); + + /* Fail if there is already an ongoing power management operation */ + if (!acquire_power_operation (self, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Use AT+CFUN=4 for power down, puts device in airplane mode */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + command, + 30, + FALSE, + (GAsyncReadyCallback) power_operation_ready, + task); +} + +static void +modem_reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CFUN=16", callback, user_data); +} + +static void +modem_power_off (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CPWROFF", callback, user_data); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CFUN=4", callback, user_data); +} + +static void +modem_power_up (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CFUN=1", callback, user_data); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + const gchar *response; + MMUnlockRetries *retries; + guint pin_attempts = 0; + guint pin2_attempts = 0; + guint puk_attempts = 0; + guint puk2_attempts = 0; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response || !mm_ublox_parse_upincnt_response (response, + &pin_attempts, &pin2_attempts, + &puk_attempts, &puk2_attempts, + error)) + return NULL; + + retries = mm_unlock_retries_new (); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin_attempts); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk_attempts); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2_attempts); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2_attempts); + + return retries; +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+UPINCNT", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Common enable/disable voice unsolicited events */ + +typedef enum { + VOICE_UNSOLICITED_EVENTS_STEP_FIRST, + VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_PRIMARY, + VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_SECONDARY, + VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_PRIMARY, + VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_SECONDARY, + VOICE_UNSOLICITED_EVENTS_STEP_LAST, +} VoiceUnsolicitedEventsStep; + +typedef struct { + gboolean enable; + VoiceUnsolicitedEventsStep step; + MMPortSerialAt *primary; + MMPortSerialAt *secondary; + gchar *ucallstat_command; + gchar *udtmfd_command; +} VoiceUnsolicitedEventsContext; + +static void +voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx) +{ + g_clear_object (&ctx->secondary); + g_clear_object (&ctx->primary); + g_free (ctx->ucallstat_command); + g_free (ctx->udtmfd_command); + g_slice_free (VoiceUnsolicitedEventsContext, ctx); +} + +static gboolean +common_voice_enable_disable_unsolicited_events_finish (MMBroadbandModemUblox *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void voice_unsolicited_events_context_step (GTask *task); + +static void +udtmfd_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + VoiceUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_full_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't %s +UUDTMFD reporting: '%s'", + ctx->enable ? "enable" : "disable", + error->message); + g_error_free (error); + } + + ctx->step++; + voice_unsolicited_events_context_step (task); +} + +static void +ucallstat_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + VoiceUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_full_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't %s +UCALLSTAT reporting: '%s'", + ctx->enable ? "enable" : "disable", + error->message); + g_error_free (error); + } + + ctx->step++; + voice_unsolicited_events_context_step (task); +} + +static void +voice_unsolicited_events_context_step (GTask *task) +{ + MMBroadbandModemUblox *self; + VoiceUnsolicitedEventsContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case VOICE_UNSOLICITED_EVENTS_STEP_FIRST: + ctx->step++; + /* fall-through */ + + case VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_PRIMARY: + if (ctx->primary) { + mm_obj_dbg (self, "%s extended call status reporting in primary port...", + ctx->enable ? "enabling" : "disabling"); + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + ctx->primary, + ctx->ucallstat_command, + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)ucallstat_ready, + task); + return; + } + ctx->step++; + /* fall-through */ + + case VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_SECONDARY: + if (ctx->secondary) { + mm_obj_dbg (self, "%s extended call status reporting in secondary port...", + ctx->enable ? "enabling" : "disabling"); + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + ctx->secondary, + ctx->ucallstat_command, + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)ucallstat_ready, + task); + return; + } + ctx->step++; + /* fall-through */ + + case VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_PRIMARY: + if ((self->priv->udtmfd_support == FEATURE_SUPPORTED) && (ctx->primary)) { + mm_obj_dbg (self, "%s DTMF detection and reporting in primary port...", + ctx->enable ? "enabling" : "disabling"); + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + ctx->primary, + ctx->udtmfd_command, + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)udtmfd_ready, + task); + return; + } + ctx->step++; + /* fall-through */ + + case VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_SECONDARY: + if ((self->priv->udtmfd_support == FEATURE_SUPPORTED) && (ctx->secondary)) { + mm_obj_dbg (self, "%s DTMF detection and reporting in secondary port...", + ctx->enable ? "enabling" : "disabling"); + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + ctx->secondary, + ctx->udtmfd_command, + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)udtmfd_ready, + task); + return; + } + ctx->step++; + /* fall-through */ + + case VOICE_UNSOLICITED_EVENTS_STEP_LAST: + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +common_voice_enable_disable_unsolicited_events (MMBroadbandModemUblox *self, + gboolean enable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + VoiceUnsolicitedEventsContext *ctx; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_slice_new0 (VoiceUnsolicitedEventsContext); + ctx->step = VOICE_UNSOLICITED_EVENTS_STEP_FIRST; + ctx->enable = enable; + if (enable) { + ctx->ucallstat_command = g_strdup ("+UCALLSTAT=1"); + ctx->udtmfd_command = g_strdup ("+UDTMFD=1,2"); + } else { + ctx->ucallstat_command = g_strdup ("+UCALLSTAT=0"); + ctx->udtmfd_command = g_strdup ("+UDTMFD=0"); + } + ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); + ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); + g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free); + + voice_unsolicited_events_context_step (task); +} + +/*****************************************************************************/ +/* SIM hot swap setup (Modem interface) */ + +typedef enum { + CIEV_SIM_STATUS_UNKNOWN = -1, + CIEV_SIM_STATUS_REMOVED, + CIEV_SIM_STATUS_INSERTED, +} MMUbloxSimInsertStatus; + +static gboolean +modem_setup_sim_hot_swap_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +ublox_ciev_unsolicited_handler (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemUblox *self) +{ + gint sim_insert_status = CIEV_SIM_STATUS_UNKNOWN; + + if (!mm_get_int_from_match_info (match_info, 1, &sim_insert_status)) { + mm_obj_dbg (self, "CIEV: unable to parse sim insert indication"); + return; + } + + mm_obj_msg (self, "CIEV: sim hot swap detected '%d'", sim_insert_status); + if (sim_insert_status == CIEV_SIM_STATUS_INSERTED || + sim_insert_status == CIEV_SIM_STATUS_REMOVED) { + mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self)); + } else { + mm_obj_warn (self, "(%s) CIEV: unable to determine sim insert status: %d", + mm_port_get_device (MM_PORT (port)), + sim_insert_status); + } +} + +static void +ublox_setup_ciev_handler (MMIfaceModem *self, + guint simind_idx) +{ + g_autoptr(GRegex) pattern = NULL; + g_autofree gchar *ciev_regex = NULL; + MMPortSerialAt *primary_port; + MMPortSerialAt *secondary_port; + + primary_port = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + mm_obj_dbg (self, "setting up simind 'CIEV: %d' events handler", simind_idx); + ciev_regex = g_strdup_printf ("\\r\\n\\+CIEV: %d,([0-1]{1})\\r\\n", simind_idx); + pattern = g_regex_new (ciev_regex, + G_REGEX_RAW | G_REGEX_OPTIMIZE, + 0, NULL); + g_assert (pattern); + mm_port_serial_at_add_unsolicited_msg_handler ( + primary_port, + pattern, + (MMPortSerialAtUnsolicitedMsgFn) ublox_ciev_unsolicited_handler, + self, + NULL); + + secondary_port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); + if (secondary_port) + mm_port_serial_at_add_unsolicited_msg_handler ( + secondary_port, + pattern, + (MMPortSerialAtUnsolicitedMsgFn) ublox_ciev_unsolicited_handler, + self, + NULL); +} + +static void +process_cind_verbosity_response (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + mm_base_modem_at_command_finish (self, res, &error); + + if (error) { + mm_obj_warn (self, "CIND: verbose mode is not configured: %s", error->message); + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "CIND unsolicited response codes processing verbosity configured successfully"); + + if (!mm_broadband_modem_sim_hot_swap_ports_context_init (MM_BROADBAND_MODEM (self), &error)) + mm_obj_warn (self, "failed to initialize SIM hot swap ports context: %s", error->message); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +typedef struct { + gchar *desc; + guint idx; + gint min; + gint max; +} CindResponse; + +static void +cind_simind_format_check_ready (MMBroadbandModem *self, + GAsyncResult *res, + GTask *task) +{ + GHashTable *indicators = NULL; + GError *error = NULL; + const gchar *result; + CindResponse *r; + + result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error || + !(indicators = mm_3gpp_parse_cind_test_response (result, &error))) { + mm_obj_dbg (self, "+CIND check failed: %s", error->message); + g_prefix_error (&error, "CIND check failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + r = g_hash_table_lookup (indicators, "simind"); + if (r) { + mm_obj_dbg (self, "simind CIEV indications are supported, indication order number: %d", r->idx); + ublox_setup_ciev_handler (MM_IFACE_MODEM (self), r->idx); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CMER=1,0,0,1,0", + 3, + FALSE, + (GAsyncReadyCallback) process_cind_verbosity_response, + task); + } else { + mm_obj_dbg (self, "simind CIEV indications are not supported"); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "simind CIEV indications are not supported"); + g_object_unref (task); + } + g_hash_table_destroy (indicators); +} + +static void +modem_setup_sim_hot_swap (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CIND=?", + 3, + TRUE, + (GAsyncReadyCallback) cind_simind_format_check_ready, + task); +} + +/*****************************************************************************/ +/* SIM hot swap cleanup (Modem interface) */ + +static void +modem_cleanup_sim_hot_swap (MMIfaceModem *self) +{ + mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self)); +} + +/*****************************************************************************/ +/* Enabling unsolicited events (Voice interface) */ + +static gboolean +modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +voice_enable_unsolicited_events_ready (MMBroadbandModemUblox *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "Couldn't enable u-blox-specific voice unsolicited events: %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + common_voice_enable_disable_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self), + TRUE, + (GAsyncReadyCallback) voice_enable_unsolicited_events_ready, + task); +} + +static void +modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's enable */ + iface_modem_voice_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Disabling unsolicited events (Voice interface) */ + +static gboolean +modem_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +voice_disable_unsolicited_events_ready (MMBroadbandModemUblox *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "Couldn't disable u-blox-specific voice unsolicited events: %s", error->message); + g_error_free (error); + } + + iface_modem_voice_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_VOICE (self), + (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready, + task); +} + +static void +modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + common_voice_enable_disable_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self), + FALSE, + (GAsyncReadyCallback) voice_disable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Common setup/cleanup voice unsolicited events */ + +static void +ucallstat_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemUblox *self) +{ + static const MMCallState ublox_call_state[] = { + [0] = MM_CALL_STATE_ACTIVE, + [1] = MM_CALL_STATE_HELD, + [2] = MM_CALL_STATE_DIALING, /* Dialing (MOC) */ + [3] = MM_CALL_STATE_RINGING_OUT, /* Alerting (MOC) */ + [4] = MM_CALL_STATE_RINGING_IN, /* Incoming (MTC) */ + [5] = MM_CALL_STATE_WAITING, /* Waiting (MTC) */ + [6] = MM_CALL_STATE_TERMINATED, + [7] = MM_CALL_STATE_ACTIVE, /* Treated same way as ACTIVE */ + }; + + MMCallInfo call_info = { 0 }; + guint aux; + + if (!mm_get_uint_from_match_info (match_info, 1, &aux)) { + mm_obj_warn (self, "couldn't parse call index from +UCALLSTAT"); + return; + } + call_info.index = aux; + + if (!mm_get_uint_from_match_info (match_info, 2, &aux) || + (aux >= G_N_ELEMENTS (ublox_call_state))) { + mm_obj_warn (self, "couldn't parse call state from +UCALLSTAT"); + return; + } + call_info.state = ublox_call_state[aux]; + + /* guess direction for some of the states */ + switch (call_info.state) { + case MM_CALL_STATE_DIALING: + case MM_CALL_STATE_RINGING_OUT: + call_info.direction = MM_CALL_DIRECTION_OUTGOING; + break; + case MM_CALL_STATE_RINGING_IN: + case MM_CALL_STATE_WAITING: + call_info.direction = MM_CALL_DIRECTION_INCOMING; + break; + case MM_CALL_STATE_UNKNOWN: + case MM_CALL_STATE_ACTIVE: + case MM_CALL_STATE_HELD: + case MM_CALL_STATE_TERMINATED: + default: + call_info.direction = MM_CALL_DIRECTION_UNKNOWN; + break; + } + + mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); +} + +static void +udtmfd_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemUblox *self) +{ + g_autofree gchar *dtmf = NULL; + + dtmf = g_match_info_fetch (match_info, 1); + mm_obj_dbg (self, "received DTMF: %s", dtmf); + /* call index unknown */ + mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf); +} + +static void +common_voice_setup_cleanup_unsolicited_events (MMBroadbandModemUblox *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + if (G_UNLIKELY (!self->priv->ucallstat_regex)) + self->priv->ucallstat_regex = g_regex_new ("\\r\\n\\+UCALLSTAT:\\s*(\\d+),(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + if (G_UNLIKELY (!self->priv->udtmfd_regex)) + self->priv->udtmfd_regex = g_regex_new ("\\r\\n\\+UUDTMFD:\\s*([0-9A-D\\*\\#])\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + self->priv->ucallstat_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)ucallstat_received : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + self->priv->udtmfd_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)udtmfd_received : NULL, + enable ? self : NULL, + NULL); + } +} + +/*****************************************************************************/ +/* Cleanup unsolicited events (Voice interface) */ + +static gboolean +modem_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "Couldn't cleanup parent voice unsolicited events: %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* our own cleanup first */ + common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self), FALSE); + + /* Chain up parent's cleanup */ + iface_modem_voice_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Setup unsolicited events (Voice interface) */ + +static gboolean +modem_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "Couldn't setup parent voice unsolicited events: %s", error->message); + g_error_free (error); + } + + /* our own setup next */ + common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self), TRUE); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* chain up parent's setup first */ + iface_modem_voice_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Create call (Voice interface) */ + +static MMBaseCall * +create_call (MMIfaceModemVoice *self, + MMCallDirection direction, + const gchar *number) +{ + return mm_base_call_new (MM_BASE_MODEM (self), + direction, + number, + TRUE, /* skip_incoming_timeout */ + TRUE, /* supports_dialing_to_ringing */ + TRUE); /* supports_ringing_to_active */ +} + +/*****************************************************************************/ +/* Check if Voice supported (Voice interface) */ + +static gboolean +modem_voice_check_support_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +udtmfd_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self); + + self->priv->udtmfd_support = (!!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ? + FEATURE_SUPPORTED : FEATURE_UNSUPPORTED); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_voice_check_support_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_voice_parent->check_support_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* voice is supported, check if +UDTMFD is available */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+UDTMFD=?", + 3, + TRUE, + (GAsyncReadyCallback) udtmfd_test_ready, + task); +} + +static void +modem_voice_check_support (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* chain up parent's setup first */ + iface_modem_voice_parent->check_support ( + self, + (GAsyncReadyCallback)parent_voice_check_support_ready, + task); +} + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +typedef enum { + CREATE_BEARER_STEP_FIRST, + CREATE_BEARER_STEP_CHECK_PROFILE, + CREATE_BEARER_STEP_CHECK_MODE, + CREATE_BEARER_STEP_CREATE_BEARER, + CREATE_BEARER_STEP_LAST, +} CreateBearerStep; + +typedef struct { + CreateBearerStep step; + MMBearerProperties *properties; + MMBaseBearer *bearer; + gboolean has_net; +} CreateBearerContext; + +static void +create_bearer_context_free (CreateBearerContext *ctx) +{ + g_clear_object (&ctx->bearer); + g_object_unref (ctx->properties); + g_slice_free (CreateBearerContext, ctx); +} + +static MMBaseBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return MM_BASE_BEARER (g_task_propagate_pointer (G_TASK (res), error)); +} + +static void create_bearer_step (GTask *task); + +static void +broadband_bearer_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemUblox *self; + CreateBearerContext *ctx; + GError *error = NULL; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + g_assert (!ctx->bearer); + ctx->bearer = mm_broadband_bearer_new_finish (res, &error); + if (!ctx->bearer) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "new generic broadband bearer created at DBus path '%s'", mm_base_bearer_get_path (ctx->bearer)); + ctx->step++; + create_bearer_step (task); +} + +static void +broadband_bearer_ublox_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemUblox *self; + CreateBearerContext *ctx; + GError *error = NULL; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + g_assert (!ctx->bearer); + ctx->bearer = mm_broadband_bearer_ublox_new_finish (res, &error); + if (!ctx->bearer) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "new u-blox broadband bearer created at DBus path '%s'", mm_base_bearer_get_path (ctx->bearer)); + ctx->step++; + create_bearer_step (task); +} + +static void +mode_check_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self); + const gchar *response; + GError *error = NULL; + CreateBearerContext *ctx; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) { + mm_obj_dbg (self, "couldn't load current networking mode: %s", error->message); + g_error_free (error); + } else if (!mm_ublox_parse_ubmconf_response (response, &self->priv->mode, &error)) { + mm_obj_dbg (self, "couldn't parse current networking mode response '%s': %s", response, error->message); + g_error_free (error); + } else { + g_assert (self->priv->mode != MM_UBLOX_NETWORKING_MODE_UNKNOWN); + mm_obj_dbg (self, "networking mode loaded: %s", mm_ublox_networking_mode_get_string (self->priv->mode)); + } + + /* If checking networking mode isn't supported, we'll fallback to + * assume the device is in router mode, which is the mode asking for + * less connection setup rules from our side (just request DHCP). + */ + if (self->priv->mode == MM_UBLOX_NETWORKING_MODE_UNKNOWN && ctx->has_net) { + mm_obj_dbg (self, "fallback to default networking mode: router"); + self->priv->mode = MM_UBLOX_NETWORKING_MODE_ROUTER; + } + + self->priv->mode_checked = TRUE; + + ctx->step++; + create_bearer_step (task); +} + +static void +profile_check_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self); + const gchar *response; + GError *error = NULL; + CreateBearerContext *ctx; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) { + mm_obj_dbg (self, "couldn't load current usb profile: %s", error->message); + g_error_free (error); + } else if (!mm_ublox_parse_uusbconf_response (response, &self->priv->profile, &error)) { + mm_obj_dbg (self, "couldn't parse current usb profile response '%s': %s", response, error->message); + g_error_free (error); + } else { + g_assert (self->priv->profile != MM_UBLOX_USB_PROFILE_UNKNOWN); + mm_obj_dbg (self, "usb profile loaded: %s", mm_ublox_usb_profile_get_string (self->priv->profile)); + } + + /* Assume the operation has been performed, even if it may have failed */ + self->priv->profile_checked = TRUE; + + ctx->step++; + create_bearer_step (task); +} + +static void +create_bearer_step (GTask *task) +{ + MMBroadbandModemUblox *self; + CreateBearerContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case CREATE_BEARER_STEP_FIRST: + ctx->step++; + /* fall through */ + + case CREATE_BEARER_STEP_CHECK_PROFILE: + if (!self->priv->profile_checked) { + mm_obj_dbg (self, "checking current USB profile..."); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+UUSBCONF?", + 3, + FALSE, + (GAsyncReadyCallback) profile_check_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case CREATE_BEARER_STEP_CHECK_MODE: + if (!self->priv->mode_checked) { + mm_obj_dbg (self, "checking current networking mode..."); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+UBMCONF?", + 3, + FALSE, + (GAsyncReadyCallback) mode_check_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case CREATE_BEARER_STEP_CREATE_BEARER: + /* If we have a net interface, we'll create a u-blox bearer, unless for + * any reason we have the back-compatible profile selected. */ + if ((self->priv->profile != MM_UBLOX_USB_PROFILE_BACK_COMPATIBLE) && ctx->has_net) { + /* whenever there is a net port, we should have loaded a valid networking mode */ + g_assert (self->priv->mode != MM_UBLOX_NETWORKING_MODE_UNKNOWN); + mm_obj_dbg (self, "creating u-blox broadband bearer (%s profile, %s mode)...", + mm_ublox_usb_profile_get_string (self->priv->profile), + mm_ublox_networking_mode_get_string (self->priv->mode)); + mm_broadband_bearer_ublox_new ( + MM_BROADBAND_MODEM (self), + self->priv->profile, + self->priv->mode, + ctx->properties, + NULL, /* cancellable */ + (GAsyncReadyCallback) broadband_bearer_ublox_new_ready, + task); + return; + } + + /* If usb profile is back-compatible already, or if there is no NET port + * available, create default generic bearer */ + mm_obj_dbg (self, "creating generic broadband bearer..."); + mm_broadband_bearer_new (MM_BROADBAND_MODEM (self), + ctx->properties, + NULL, /* cancellable */ + (GAsyncReadyCallback) broadband_bearer_new_ready, + task); + return; + + case CREATE_BEARER_STEP_LAST: + g_assert (ctx->bearer); + g_task_return_pointer (task, g_object_ref (ctx->bearer), g_object_unref); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } + + g_assert_not_reached (); +} + +static void +modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CreateBearerContext *ctx; + GTask *task; + + ctx = g_slice_new0 (CreateBearerContext); + ctx->step = CREATE_BEARER_STEP_FIRST; + ctx->properties = g_object_ref (properties); + + /* Flag whether this modem has exposed a network interface */ + ctx->has_net = !!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify) create_bearer_context_free); + create_bearer_step (task); +} + +/*****************************************************************************/ +/* Create SIM (Modem interface) */ + +static MMBaseSim * +modem_create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_ublox_new_finish (res, error); +} + +static void +modem_create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_sim_ublox_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *_self) +{ + MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self); + MMPortSerialAt *ports[2]; + guint i; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_ublox_parent_class)->setup_ports (_self); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Configure AT ports */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + g_object_set (ports[i], + MM_PORT_SERIAL_SEND_DELAY, (guint64) 0, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->pbready_regex, + NULL, NULL, NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemUblox * +mm_broadband_modem_ublox_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_UBLOX, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + /* Generic bearer (TTY) and u-blox bearer (NET) supported */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_ublox_init (MMBroadbandModemUblox *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_UBLOX, + MMBroadbandModemUbloxPrivate); + self->priv->profile = MM_UBLOX_USB_PROFILE_UNKNOWN; + self->priv->mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN; + self->priv->any_allowed = MM_MODEM_MODE_NONE; + self->priv->support_config.loaded = FALSE; + self->priv->support_config.method = SETTINGS_UPDATE_METHOD_UNKNOWN; + self->priv->support_config.uact = FEATURE_SUPPORT_UNKNOWN; + self->priv->support_config.ubandsel = FEATURE_SUPPORT_UNKNOWN; + self->priv->udtmfd_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->pbready_regex = g_regex_new ("\\r\\n\\+PBREADY\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface->create_sim = modem_create_sim; + iface->create_sim_finish = modem_create_sim_finish; + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; + iface->load_power_state = load_power_state; + iface->load_power_state_finish = load_power_state_finish; + iface->modem_power_up = modem_power_up; + iface->modem_power_up_finish = common_modem_power_operation_finish; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = common_modem_power_operation_finish; + iface->modem_power_off = modem_power_off; + iface->modem_power_off_finish = common_modem_power_operation_finish; + iface->reset = modem_reset; + iface->reset_finish = common_modem_power_operation_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = common_set_current_modes_bands_finish; + iface->load_supported_bands = load_supported_bands; + iface->load_supported_bands_finish = load_supported_bands_finish; + iface->load_current_bands = load_current_bands; + iface->load_current_bands_finish = load_current_bands_finish; + iface->set_current_bands = set_current_bands; + iface->set_current_bands_finish = common_set_current_modes_bands_finish; + iface->setup_sim_hot_swap = modem_setup_sim_hot_swap; + iface->setup_sim_hot_swap_finish = modem_setup_sim_hot_swap_finish; + iface->cleanup_sim_hot_swap = modem_cleanup_sim_hot_swap; +} + +static void +iface_modem_voice_init (MMIfaceModemVoice *iface) +{ + iface_modem_voice_parent = g_type_interface_peek_parent (iface); + + iface->check_support = modem_voice_check_support; + iface->check_support_finish = modem_voice_check_support_finish; + iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_voice_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_voice_cleanup_unsolicited_events_finish; + iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_voice_disable_unsolicited_events_finish; + + iface->create_call = create_call; +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (object); + + g_regex_unref (self->priv->pbready_regex); + + if (self->priv->ucallstat_regex) + g_regex_unref (self->priv->ucallstat_regex); + if (self->priv->udtmfd_regex) + g_regex_unref (self->priv->udtmfd_regex); + + G_OBJECT_CLASS (mm_broadband_modem_ublox_parent_class)->finalize (object); +} + +static void +mm_broadband_modem_ublox_class_init (MMBroadbandModemUbloxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemUbloxPrivate)); + + object_class->finalize = finalize; + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/ublox/mm-broadband-modem-ublox.h b/src/plugins/ublox/mm-broadband-modem-ublox.h new file mode 100644 index 00000000..f1c6bbcf --- /dev/null +++ b/src/plugins/ublox/mm-broadband-modem-ublox.h @@ -0,0 +1,49 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_UBLOX_H +#define MM_BROADBAND_MODEM_UBLOX_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_UBLOX (mm_broadband_modem_ublox_get_type ()) +#define MM_BROADBAND_MODEM_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_UBLOX, MMBroadbandModemUblox)) +#define MM_BROADBAND_MODEM_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_UBLOX, MMBroadbandModemUbloxClass)) +#define MM_IS_BROADBAND_MODEM_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_UBLOX)) +#define MM_IS_BROADBAND_MODEM_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_UBLOX)) +#define MM_BROADBAND_MODEM_UBLOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_UBLOX, MMBroadbandModemUbloxClass)) + +typedef struct _MMBroadbandModemUblox MMBroadbandModemUblox; +typedef struct _MMBroadbandModemUbloxClass MMBroadbandModemUbloxClass; +typedef struct _MMBroadbandModemUbloxPrivate MMBroadbandModemUbloxPrivate; + +struct _MMBroadbandModemUblox { + MMBroadbandModem parent; + MMBroadbandModemUbloxPrivate *priv; +}; + +struct _MMBroadbandModemUbloxClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_ublox_get_type (void); + +MMBroadbandModemUblox *mm_broadband_modem_ublox_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_UBLOX_H */ diff --git a/src/plugins/ublox/mm-modem-helpers-ublox.c b/src/plugins/ublox/mm-modem-helpers-ublox.c new file mode 100644 index 00000000..84a7cf27 --- /dev/null +++ b/src/plugins/ublox/mm-modem-helpers-ublox.c @@ -0,0 +1,2014 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> +#include <string.h> + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-ublox.h" + +/*****************************************************************************/ +/* +UPINCNT response parser */ + +gboolean +mm_ublox_parse_upincnt_response (const gchar *response, + guint *out_pin_attempts, + guint *out_pin2_attempts, + guint *out_puk_attempts, + guint *out_puk2_attempts, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + guint pin_attempts = 0; + guint pin2_attempts = 0; + guint puk_attempts = 0; + guint puk2_attempts = 0; + gboolean success = TRUE; + + g_assert (out_pin_attempts); + g_assert (out_pin2_attempts); + g_assert (out_puk_attempts); + g_assert (out_puk2_attempts); + + /* Response may be e.g.: + * +UPINCNT: 3,3,10,10 + */ + r = g_regex_new ("\\+UPINCNT: (\\d+),(\\d+),(\\d+),(\\d+)(?:\\r\\n)?", 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + if (!mm_get_uint_from_match_info (match_info, 1, &pin_attempts)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Couldn't parse PIN attempts"); + goto out; + } + if (!mm_get_uint_from_match_info (match_info, 2, &pin2_attempts)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Couldn't parse PIN2 attempts"); + goto out; + } + if (!mm_get_uint_from_match_info (match_info, 3, &puk_attempts)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Couldn't parse PUK attempts"); + goto out; + } + if (!mm_get_uint_from_match_info (match_info, 4, &puk2_attempts)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Couldn't parse PUK2 attempts"); + goto out; + } + success = TRUE; + } + +out: + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (!success) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse +UPINCNT response: '%s'", response); + return FALSE; + } + + *out_pin_attempts = pin_attempts; + *out_pin2_attempts = pin2_attempts; + *out_puk_attempts = puk_attempts; + *out_puk2_attempts = puk2_attempts; + return TRUE; +} + +/*****************************************************************************/ +/* UUSBCONF? response parser */ + +gboolean +mm_ublox_parse_uusbconf_response (const gchar *response, + MMUbloxUsbProfile *out_profile, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + MMUbloxUsbProfile profile = MM_UBLOX_USB_PROFILE_UNKNOWN; + + g_assert (out_profile != NULL); + + /* Response may be e.g.: + * +UUSBCONF: 3,"RNDIS",,"0x1146" + * +UUSBCONF: 2,"ECM",,"0x1143" + * +UUSBCONF: 0,"",,"0x1141" + * + * Note: we don't rely on the PID; assuming future new modules will + * have a different PID but they may keep the profile names. + */ + r = g_regex_new ("\\+UUSBCONF: (\\d+),([^,]*),([^,]*),([^,]*)(?:\\r\\n)?", 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + g_autofree gchar *profile_name = NULL; + + profile_name = mm_get_string_unquoted_from_match_info (match_info, 2); + if (profile_name && profile_name[0]) { + if (g_str_equal (profile_name, "RNDIS")) + profile = MM_UBLOX_USB_PROFILE_RNDIS; + else if (g_str_equal (profile_name, "ECM")) + profile = MM_UBLOX_USB_PROFILE_ECM; + else + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Unknown USB profile: '%s'", profile_name); + } else + profile = MM_UBLOX_USB_PROFILE_BACK_COMPATIBLE; + } + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (profile == MM_UBLOX_USB_PROFILE_UNKNOWN) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse profile response"); + return FALSE; + } + + *out_profile = profile; + return TRUE; +} + +/*****************************************************************************/ +/* UBMCONF? response parser */ + +gboolean +mm_ublox_parse_ubmconf_response (const gchar *response, + MMUbloxNetworkingMode *out_mode, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + MMUbloxNetworkingMode mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN; + + g_assert (out_mode != NULL); + + /* Response may be e.g.: + * +UBMCONF: 1 + * +UBMCONF: 2 + */ + r = g_regex_new ("\\+UBMCONF: (\\d+)(?:\\r\\n)?", 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + guint mode_id = 0; + + if (mm_get_uint_from_match_info (match_info, 1, &mode_id)) { + switch (mode_id) { + case 1: + mode = MM_UBLOX_NETWORKING_MODE_ROUTER; + break; + case 2: + mode = MM_UBLOX_NETWORKING_MODE_BRIDGE; + break; + default: + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Unknown mode id: '%u'", mode_id); + break; + } + } + } + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (mode == MM_UBLOX_NETWORKING_MODE_UNKNOWN) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse networking mode response"); + return FALSE; + } + + *out_mode = mode; + return TRUE; +} + +/*****************************************************************************/ +/* UIPADDR=N response parser */ + +gboolean +mm_ublox_parse_uipaddr_response (const gchar *response, + guint *out_cid, + gchar **out_if_name, + gchar **out_ipv4_address, + gchar **out_ipv4_subnet, + gchar **out_ipv6_global_address, + gchar **out_ipv6_link_local_address, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + guint cid = 0; + g_autofree gchar *if_name = NULL; + g_autofree gchar *ipv4_address = NULL; + g_autofree gchar *ipv4_subnet = NULL; + g_autofree gchar *ipv6_global_address = NULL; + g_autofree gchar *ipv6_link_local_address = NULL; + + /* Response may be e.g.: + * +UIPADDR: 1,"ccinet0","5.168.120.13","255.255.255.0","","" + * +UIPADDR: 2,"ccinet1","","","2001::2:200:FF:FE00:0/64","FE80::200:FF:FE00:0/64" + * +UIPADDR: 3,"ccinet2","5.10.100.2","255.255.255.0","2001::1:200:FF:FE00:0/64","FE80::200:FF:FE00:0/64" + * + * We assume only ONE line is returned; because we request +UIPADDR with a specific N CID. + */ + r = g_regex_new ("\\+UIPADDR: (\\d+),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*)(?:\\r\\n)?", 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (!g_match_info_matches (match_info)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Couldn't match +UIPADDR response"); + return FALSE; + } + + if (out_cid && !mm_get_uint_from_match_info (match_info, 1, &cid)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing cid"); + return FALSE; + } + + if (out_if_name && !(if_name = mm_get_string_unquoted_from_match_info (match_info, 2))) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing interface name"); + return FALSE; + } + + /* Remaining strings are optional */ + ipv4_address = mm_get_string_unquoted_from_match_info (match_info, 3); + ipv4_subnet = mm_get_string_unquoted_from_match_info (match_info, 4); + ipv6_global_address = mm_get_string_unquoted_from_match_info (match_info, 5); + ipv6_link_local_address = mm_get_string_unquoted_from_match_info (match_info, 6); + + if (out_cid) + *out_cid = cid; + if (out_if_name) + *out_if_name = g_steal_pointer (&if_name); + if (out_ipv4_address) + *out_ipv4_address = g_steal_pointer (&ipv4_address); + if (out_ipv4_subnet) + *out_ipv4_subnet = g_steal_pointer (&ipv4_subnet); + if (out_ipv6_global_address) + *out_ipv6_global_address = g_steal_pointer (&ipv6_global_address); + if (out_ipv6_link_local_address) + *out_ipv6_link_local_address = g_steal_pointer (&ipv6_link_local_address); + return TRUE; +} + +/*****************************************************************************/ +/* CFUN? response parser */ + +gboolean +mm_ublox_parse_cfun_response (const gchar *response, + MMModemPowerState *out_state, + GError **error) +{ + guint state; + + if (!mm_3gpp_parse_cfun_query_response (response, &state, error)) + return FALSE; + + switch (state) { + case 1: + *out_state = MM_MODEM_POWER_STATE_ON; + return TRUE; + case 0: + /* minimum functionality */ + case 4: + /* airplane mode */ + case 19: + /* minimum functionality with SIM deactivated */ + *out_state = MM_MODEM_POWER_STATE_LOW; + return TRUE; + default: + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown +CFUN state: %u", state); + return FALSE; + } +} + +/*****************************************************************************/ +/* URAT=? response parser */ + +/* Index of the array is the ublox-specific value */ +static const MMModemMode ublox_combinations[] = { + ( MM_MODEM_MODE_2G ), + ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ), + ( MM_MODEM_MODE_3G ), + ( MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_4G ), +}; + +GArray * +mm_ublox_parse_urat_test_response (const gchar *response, + gpointer log_object, + GError **error) +{ + GArray *combinations = NULL; + GArray *selected = NULL; + GArray *preferred = NULL; + gchar **split; + guint split_len; + GError *inner_error = NULL; + guint i; + + /* + * E.g.: + * AT+URAT=? + * +URAT: (0-6),(0,2,3) + */ + response = mm_strip_tag (response, "+URAT:"); + split = mm_split_string_groups (response); + split_len = g_strv_length (split); + if (split_len > 2 || split_len < 1) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unexpected number of groups in +URAT=? response: %u", g_strv_length (split)); + goto out; + } + + /* The selected list must have values */ + selected = mm_parse_uint_list (split[0], &inner_error); + if (inner_error) + goto out; + + if (!selected) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No selected RAT values given in +URAT=? response"); + goto out; + } + + /* For our purposes, the preferred list may be empty */ + preferred = mm_parse_uint_list (split[1], &inner_error); + if (inner_error) + goto out; + + /* Build array of combinations */ + combinations = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination)); + + for (i = 0; i < selected->len; i++) { + guint selected_value; + MMModemModeCombination combination; + guint j; + + selected_value = g_array_index (selected, guint, i); + if (selected_value >= G_N_ELEMENTS (ublox_combinations)) { + mm_obj_warn (log_object, "unexpected AcT value: %u", selected_value); + continue; + } + + /* Combination without any preferred */ + combination.allowed = ublox_combinations[selected_value]; + combination.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, combination); + + if (mm_count_bits_set (combination.allowed) == 1) + continue; + + if (!preferred) + continue; + + for (j = 0; j < preferred->len; j++) { + guint preferred_value; + + preferred_value = g_array_index (preferred, guint, j); + if (preferred_value >= G_N_ELEMENTS (ublox_combinations)) { + mm_obj_warn (log_object, "unexpected AcT preferred value: %u", preferred_value); + continue; + } + combination.preferred = ublox_combinations[preferred_value]; + if (mm_count_bits_set (combination.preferred) != 1) { + mm_obj_warn (log_object, "AcT preferred value should be a single AcT: %u", preferred_value); + continue; + } + if (!(combination.allowed & combination.preferred)) + continue; + g_array_append_val (combinations, combination); + } + } + + if (combinations->len == 0) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No combinations built from +URAT=? response"); + goto out; + } + +out: + g_strfreev (split); + if (selected) + g_array_unref (selected); + if (preferred) + g_array_unref (preferred); + + if (inner_error) { + if (combinations) + g_array_unref (combinations); + g_propagate_error (error, inner_error); + return NULL; + } + + return combinations; +} + +typedef struct { + const gchar *model; + SettingsUpdateMethod method; + FeatureSupport uact; + FeatureSupport ubandsel; + MMModemMode mode; + MMModemBand bands_2g[4]; + MMModemBand bands_3g[6]; + MMModemBand bands_4g[12]; +} BandConfiguration; + +static const BandConfiguration band_configuration[] = { + { + .model = "SARA-G300", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS } + }, + { + .model = "SARA-G310", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS } + }, + { + .model = "SARA-G340", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS } + }, + { + .model = "SARA-G350", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS } + }, + { + .model = "SARA-G450", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS } + }, + { + .model = "LISA-U200", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "LISA-U201", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "LISA-U230", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "LISA-U260", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 } + }, + { + .model = "LISA-U270", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "SARA-U201", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "SARA-U260", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 } + }, + { + .model = "SARA-U270", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "SARA-U280", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 } + }, + { + .model = "MPCI-L201", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 } + }, + { + .model = "MPCI-L200", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_4, + MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_17 } + }, + { + .model = "MPCI-L210", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "MPCI-L220", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_19 } + }, + { + .model = "MPCI-L280", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 } + }, + { + .model = "TOBY-L200", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_4, + MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_17 } + }, + { + .model = "TOBY-L201", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 } + }, + { + .model = "TOBY-L210", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "TOBY-L220", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_19 } + }, + { + .model = "TOBY-L280", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 } + }, + { + .model = "TOBY-L4006", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_SUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_29 } + }, + { + .model = "TOBY-L4106", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_SUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, + MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_38 } + }, + { + .model = "TOBY-L4206", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_SUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_9, + MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_28 } + }, + { + .model = "TOBY-L4906", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_SUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_39, + MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41 } + }, + { + .model = "TOBY-R200", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_12 } + }, + { + .model = "TOBY-R202", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_12 } + }, + { + .model = "LARA-R202", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_12 } + }, + { + .model = "LARA-R203", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_12 } + }, + { + .model = "LARA-R204", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_13 } + }, + { + .model = "LARA-R211", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "LARA-R280", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 } + }, + { + .model = "LARA-R3121", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "SARA-N200", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_8 } + }, + { + .model = "SARA-N201", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_5 } + }, + { + .model = "SARA-N210", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "SARA-N211", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "SARA-N280", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_28 } + }, + { + .model = "SARA-R410M-52B", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13 } + }, + { + .model = "SARA-R410M-02B", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19, + MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_39 } + }, + { + .model = "SARA-R412M-02B", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19, + MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_39 } + }, + { + .model = "SARA-N410-02B", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19, + MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28 } + }, +}; + +gboolean +mm_ublox_get_support_config (const gchar *model, + UbloxSupportConfig *config, + GError **error) +{ + guint i; + + if (!model) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Support configuration unknown for unknown model"); + return FALSE; + } + + for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) { + /* NOTE: matching by prefix! */ + if (g_str_has_prefix (model, band_configuration[i].model)) { + config->loaded = TRUE; + config->method = band_configuration[i].method; + config->uact = band_configuration[i].uact; + config->ubandsel = band_configuration[i].ubandsel; + return TRUE; + } + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No support configuration found for modem: %s", model); + return FALSE; +} + +/*****************************************************************************/ +/* Supported modes loading */ + +static MMModemMode +supported_modes_per_model (const gchar *model) +{ + MMModemMode mode; + guint i; + + mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G; + + if (model) { + for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) + if (g_str_has_prefix (model, band_configuration[i].model)) { + mode = band_configuration[i].mode; + return mode;; + } + } + + return mode; +} + +GArray * +mm_ublox_filter_supported_modes (const gchar *model, + GArray *combinations, + gpointer logger, + GError **error) +{ + MMModemModeCombination mode; + GArray *all; + GArray *filtered; + + /* Model not specified? */ + if (!model) + return combinations; + + /* AT+URAT=? lies; we need an extra per-device filtering, thanks u-blox. + * Don't know all PIDs for all devices, so model string based filtering... */ + + mode.allowed = supported_modes_per_model (model); + mode.preferred = MM_MODEM_MODE_NONE; + + /* Nothing filtered? */ + if (mode.allowed == supported_modes_per_model (NULL)) + return combinations; + + all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1); + g_array_append_val (all, mode); + filtered = mm_filter_supported_modes (all, combinations, logger); + g_array_unref (all); + g_array_unref (combinations); + + /* Error if nothing left */ + if (filtered->len == 0) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No valid mode combinations built after filtering (model %s)", model); + g_array_unref (filtered); + return NULL; + } + + return filtered; +} + +/*****************************************************************************/ +/* Supported bands loading */ + +GArray * +mm_ublox_get_supported_bands (const gchar *model, + gpointer log_object, + GError **error) +{ + MMModemMode mode; + GArray *bands; + guint i, j; + + mode = supported_modes_per_model (model); + bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand)); + + for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) { + if (g_str_has_prefix (model, band_configuration[i].model)) { + mm_obj_dbg (log_object, "known supported bands found for model: %s", band_configuration[i].model); + break; + } + } + + if (i == G_N_ELEMENTS (band_configuration)) { + mm_obj_warn (log_object, "unknown model name given when looking for supported bands: %s", model); + return NULL; + } + + mode = band_configuration[i].mode; + + if (mode & MM_MODEM_MODE_2G) { + for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_2g) && band_configuration[i].bands_2g[j]; j++) { + bands = g_array_append_val (bands, band_configuration[i].bands_2g[j]); + } + } + + if (mode & MM_MODEM_MODE_3G) { + for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_3g) && band_configuration[i].bands_3g[j]; j++) { + bands = g_array_append_val (bands, band_configuration[i].bands_3g[j]); + } + } + + if (mode & MM_MODEM_MODE_4G) { + for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_4g) && band_configuration[i].bands_4g[j]; j++) { + bands = g_array_append_val (bands, band_configuration[i].bands_4g[j]); + } + } + + if (bands->len == 0) { + g_array_unref (bands); + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No valid supported bands loaded"); + return NULL; + } + + return bands; +} + +typedef struct { + guint num; + MMModemBand band[4]; +} NumToBand; + +/* 2G GSM Band Frequencies */ +static const NumToBand num_bands_2g [] = { + { .num = 850, .band = { MM_MODEM_BAND_G850 } }, + { .num = 900, .band = { MM_MODEM_BAND_EGSM } }, + { .num = 1900, .band = { MM_MODEM_BAND_PCS } }, + { .num = 1800, .band = { MM_MODEM_BAND_DCS } }, +}; + +/* 3G UMTS Band Frequencies */ +static const NumToBand num_bands_3g [] = { + { .num = 800, .band = { MM_MODEM_BAND_UTRAN_6 } }, + { .num = 850, .band = { MM_MODEM_BAND_UTRAN_5 } }, + { .num = 900, .band = { MM_MODEM_BAND_UTRAN_8 } }, + { .num = 1700, .band = { MM_MODEM_BAND_UTRAN_4 } }, + { .num = 1900, .band = { MM_MODEM_BAND_UTRAN_2 } }, + { .num = 2100, .band = { MM_MODEM_BAND_UTRAN_1 } }, +}; + +/* 4G LTE Band Frequencies */ +static const NumToBand num_bands_4g [] = { + { .num = 700, .band = { MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 } }, + { .num = 800, .band = { MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_27 } }, + { .num = 850, .band = { MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_26 } }, + { .num = 900, .band = { MM_MODEM_BAND_EUTRAN_8 } }, + { .num = 1700, .band = { MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_10 } }, + { .num = 1800, .band = { MM_MODEM_BAND_EUTRAN_3 } }, + { .num = 1900, .band = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_39 } }, + { .num = 2100, .band = { MM_MODEM_BAND_EUTRAN_1 } }, + { .num = 2300, .band = { MM_MODEM_BAND_EUTRAN_40 } }, + { .num = 2500, .band = { MM_MODEM_BAND_EUTRAN_41 } }, + { .num = 2600, .band = { MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_38 } }, +}; +/*****************************************************************************/ +/* +UBANDSEL? response parser */ + +static MMModemBand +num_to_band_2g (guint num) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (num_bands_2g); i++) { + if (num == num_bands_2g[i].num) + return num_bands_2g[i].band[0]; + } + return MM_MODEM_BAND_UNKNOWN; +} + +static MMModemBand +num_to_band_3g (guint num) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (num_bands_3g); i++) { + if (num == num_bands_3g[i].num) + return num_bands_3g[i].band[0]; + } + return MM_MODEM_BAND_UNKNOWN; +} + +static guint +band_to_num (MMModemBand band) +{ + guint i, j; + + /* Search 2G list */ + for (i = 0; i < G_N_ELEMENTS (num_bands_2g); i++) { + for (j = 0; j < G_N_ELEMENTS (num_bands_2g[i].band) && num_bands_2g[i].band[j]; j++) { + if (band == num_bands_2g[i].band[j]) + return num_bands_2g[i].num; + } + } + + /* Search 3G list */ + for (i = 0; i < G_N_ELEMENTS (num_bands_3g); i++) { + for (j = 0; j < G_N_ELEMENTS (num_bands_3g[i].band) && num_bands_3g[i].band[j]; j++) { + if (band == num_bands_3g[i].band[j]) + return num_bands_3g[i].num; + } + } + + /* Search 4G list */ + for (i = 0; i < G_N_ELEMENTS (num_bands_4g); i++) { + for (j = 0; j < G_N_ELEMENTS (num_bands_4g[i].band) && num_bands_4g[i].band[j]; j++) { + if (band == num_bands_4g[i].band[j]) + return num_bands_4g[i].num; + } + } + + /* Should never happen */ + return 0; +} + +static void +append_bands (GArray *bands, + guint ubandsel_value, + MMModemMode mode, + const gchar *model, + gpointer log_object) +{ + guint i, j, k, x; + MMModemBand band; + + /* Find Modem Model Index in band_configuration */ + for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) { + if (g_str_has_prefix (model, band_configuration[i].model)) { + mm_obj_dbg (log_object, "known bands found for model: %s", band_configuration[i].model); + break; + } + } + + if (i == G_N_ELEMENTS (band_configuration)) { + mm_obj_warn (log_object, "unknown model name given when looking for bands: %s", model); + return; + } + + if (mode & MM_MODEM_MODE_2G) { + band = num_to_band_2g (ubandsel_value); + if (band != MM_MODEM_BAND_UNKNOWN) { + for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_2g); x++) { + if (band_configuration[i].bands_2g[x] == band) { + g_array_append_val (bands, band); + break; + } + } + } + } + + if (mode & MM_MODEM_MODE_3G) { + band = num_to_band_3g (ubandsel_value); + if (band != MM_MODEM_BAND_UNKNOWN) { + for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_3g); x++) { + if (band_configuration[i].bands_3g[x] == band) { + g_array_append_val (bands, band); + break; + } + } + } + } + + /* Note: The weird code segment below is to separate out specific LTE bands since + * UBANDSEL? reports back the frequency of the band and not the band itself. + */ + + if (mode & MM_MODEM_MODE_4G) { + for (j = 0; j < G_N_ELEMENTS (num_bands_4g); j++) { + if (ubandsel_value == num_bands_4g[j].num) { + for (k = 0; k < G_N_ELEMENTS (num_bands_4g[j].band); k++) { + band = num_bands_4g[j].band[k]; + if (band != MM_MODEM_BAND_UNKNOWN) { + for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_4g); x++) { + if (band_configuration[i].bands_4g[x] == band) { + g_array_append_val (bands, band); + break; + } + } + } + } + break; + } + } + } +} + +GArray * +mm_ublox_parse_ubandsel_response (const gchar *response, + const gchar *model, + gpointer log_object, + GError **error) +{ + GArray *array_values = NULL; + GArray *array = NULL; + gchar *dupstr = NULL; + GError *inner_error = NULL; + guint i; + MMModemMode mode; + + if (!g_str_has_prefix (response, "+UBANDSEL")) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse +UBANDSEL response: '%s'", response); + goto out; + } + + /* Response may be e.g.: + * +UBANDSEL: 850,900,1800,1900 + */ + dupstr = g_strchomp (g_strdup (mm_strip_tag (response, "+UBANDSEL:"))); + + array_values = mm_parse_uint_list (dupstr, &inner_error); + if (!array_values) + goto out; + + /* Convert list of ubandsel numbers to MMModemBand values */ + mode = supported_modes_per_model (model); + array = g_array_new (FALSE, FALSE, sizeof (MMModemBand)); + for (i = 0; i < array_values->len; i++) + append_bands (array, g_array_index (array_values, guint, i), mode, model, log_object); + + if (!array->len) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No known band selection values matched in +UBANDSEL response: '%s'", response); + goto out; + } + +out: + if (inner_error) { + g_propagate_error (error, inner_error); + g_clear_pointer (&array, g_array_unref); + } + g_clear_pointer (&array_values, g_array_unref); + g_free (dupstr); + return array; +} + +/*****************************************************************************/ +/* UBANDSEL=X command builder */ + +static gint +ubandsel_num_cmp (const guint *a, const guint *b) +{ + return (*a - *b); +} + +gchar * +mm_ublox_build_ubandsel_set_command (GArray *bands, + const gchar *model, + GError **error) +{ + GString *command = NULL; + GArray *ubandsel_nums; + guint num; + guint i, j, k; + + if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) + return g_strdup ("+UBANDSEL=0"); + + for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) { + if (g_str_has_prefix (model, band_configuration[i].model)) + break; + } + + if (i == G_N_ELEMENTS (band_configuration)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown modem model %s", model); + return NULL; + } + + ubandsel_nums = g_array_sized_new (FALSE, FALSE, sizeof (guint), bands->len); + + for (j = 0; j < bands->len; j++) { + MMModemBand band; + gboolean found = FALSE; + + band = g_array_index (bands, MMModemBand, j); + + /* Check to see if band is supported by the model */ + for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_2g) && band_configuration[i].bands_2g[k]; k++) { + if (band == band_configuration[i].bands_2g[k]) + found = TRUE; + } + + for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_3g) && band_configuration[i].bands_3g[k]; k++) { + if (band == band_configuration[i].bands_3g[k]) + found = TRUE; + } + + for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_4g) && band_configuration[i].bands_4g[k]; k++) { + if (band == band_configuration[i].bands_4g[k]) + found = TRUE; + } + + if (found) { + num = band_to_num (band); + g_assert (num != 0); + g_array_append_val (ubandsel_nums, num); + } + } + + if (ubandsel_nums->len == 0) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Given band combination is unsupported"); + g_array_unref (ubandsel_nums); + return NULL; + } + + if (ubandsel_nums->len > 1) + g_array_sort (ubandsel_nums, (GCompareFunc) ubandsel_num_cmp); + + /* Build command */ + command = g_string_new ("+UBANDSEL="); + for (i = 0; i < ubandsel_nums->len; i++) + g_string_append_printf (command, "%s%u", i == 0 ? "" : ",", g_array_index (ubandsel_nums, guint, i)); + + return g_string_free (command, FALSE); +} + +/*****************************************************************************/ +/* Get mode to apply when ANY */ + +MMModemMode +mm_ublox_get_modem_mode_any (const GArray *combinations) +{ + guint i; + MMModemMode any = MM_MODEM_MODE_NONE; + guint any_bits_set = 0; + + for (i = 0; i < combinations->len; i++) { + MMModemModeCombination *combination; + guint bits_set; + + combination = &g_array_index (combinations, MMModemModeCombination, i); + if (combination->preferred != MM_MODEM_MODE_NONE) + continue; + bits_set = mm_count_bits_set (combination->allowed); + if (bits_set > any_bits_set) { + any_bits_set = bits_set; + any = combination->allowed; + } + } + + /* If combinations were processed via mm_ublox_parse_urat_test_response(), + * we're sure that there will be at least one combination with preferred + * 'none', so there must be some valid combination as result */ + g_assert (any != MM_MODEM_MODE_NONE); + return any; +} + +/*****************************************************************************/ +/* UACT common config */ + +typedef struct { + guint num; + MMModemBand band; +} UactBandConfig; + +static const UactBandConfig uact_band_config[] = { + /* GSM bands */ + { .num = 900, .band = MM_MODEM_BAND_EGSM }, + { .num = 1800, .band = MM_MODEM_BAND_DCS }, + { .num = 1900, .band = MM_MODEM_BAND_PCS }, + { .num = 850, .band = MM_MODEM_BAND_G850 }, + { .num = 450, .band = MM_MODEM_BAND_G450 }, + { .num = 480, .band = MM_MODEM_BAND_G480 }, + { .num = 750, .band = MM_MODEM_BAND_G750 }, + { .num = 380, .band = MM_MODEM_BAND_G380 }, + { .num = 410, .band = MM_MODEM_BAND_G410 }, + { .num = 710, .band = MM_MODEM_BAND_G710 }, + { .num = 810, .band = MM_MODEM_BAND_G810 }, + /* UMTS bands */ + { .num = 1, .band = MM_MODEM_BAND_UTRAN_1 }, + { .num = 2, .band = MM_MODEM_BAND_UTRAN_2 }, + { .num = 3, .band = MM_MODEM_BAND_UTRAN_3 }, + { .num = 4, .band = MM_MODEM_BAND_UTRAN_4 }, + { .num = 5, .band = MM_MODEM_BAND_UTRAN_5 }, + { .num = 6, .band = MM_MODEM_BAND_UTRAN_6 }, + { .num = 7, .band = MM_MODEM_BAND_UTRAN_7 }, + { .num = 8, .band = MM_MODEM_BAND_UTRAN_8 }, + { .num = 9, .band = MM_MODEM_BAND_UTRAN_9 }, + { .num = 10, .band = MM_MODEM_BAND_UTRAN_10 }, + { .num = 11, .band = MM_MODEM_BAND_UTRAN_11 }, + { .num = 12, .band = MM_MODEM_BAND_UTRAN_12 }, + { .num = 13, .band = MM_MODEM_BAND_UTRAN_13 }, + { .num = 14, .band = MM_MODEM_BAND_UTRAN_14 }, + { .num = 19, .band = MM_MODEM_BAND_UTRAN_19 }, + { .num = 20, .band = MM_MODEM_BAND_UTRAN_20 }, + { .num = 21, .band = MM_MODEM_BAND_UTRAN_21 }, + { .num = 22, .band = MM_MODEM_BAND_UTRAN_22 }, + { .num = 25, .band = MM_MODEM_BAND_UTRAN_25 }, + /* LTE bands */ + { .num = 101, .band = MM_MODEM_BAND_EUTRAN_1 }, + { .num = 102, .band = MM_MODEM_BAND_EUTRAN_2 }, + { .num = 103, .band = MM_MODEM_BAND_EUTRAN_3 }, + { .num = 104, .band = MM_MODEM_BAND_EUTRAN_4 }, + { .num = 105, .band = MM_MODEM_BAND_EUTRAN_5 }, + { .num = 106, .band = MM_MODEM_BAND_EUTRAN_6 }, + { .num = 107, .band = MM_MODEM_BAND_EUTRAN_7 }, + { .num = 108, .band = MM_MODEM_BAND_EUTRAN_8 }, + { .num = 109, .band = MM_MODEM_BAND_EUTRAN_9 }, + { .num = 110, .band = MM_MODEM_BAND_EUTRAN_10 }, + { .num = 111, .band = MM_MODEM_BAND_EUTRAN_11 }, + { .num = 112, .band = MM_MODEM_BAND_EUTRAN_12 }, + { .num = 113, .band = MM_MODEM_BAND_EUTRAN_13 }, + { .num = 114, .band = MM_MODEM_BAND_EUTRAN_14 }, + { .num = 117, .band = MM_MODEM_BAND_EUTRAN_17 }, + { .num = 118, .band = MM_MODEM_BAND_EUTRAN_18 }, + { .num = 119, .band = MM_MODEM_BAND_EUTRAN_19 }, + { .num = 120, .band = MM_MODEM_BAND_EUTRAN_20 }, + { .num = 121, .band = MM_MODEM_BAND_EUTRAN_21 }, + { .num = 122, .band = MM_MODEM_BAND_EUTRAN_22 }, + { .num = 123, .band = MM_MODEM_BAND_EUTRAN_23 }, + { .num = 124, .band = MM_MODEM_BAND_EUTRAN_24 }, + { .num = 125, .band = MM_MODEM_BAND_EUTRAN_25 }, + { .num = 126, .band = MM_MODEM_BAND_EUTRAN_26 }, + { .num = 127, .band = MM_MODEM_BAND_EUTRAN_27 }, + { .num = 128, .band = MM_MODEM_BAND_EUTRAN_28 }, + { .num = 129, .band = MM_MODEM_BAND_EUTRAN_29 }, + { .num = 130, .band = MM_MODEM_BAND_EUTRAN_30 }, + { .num = 131, .band = MM_MODEM_BAND_EUTRAN_31 }, + { .num = 132, .band = MM_MODEM_BAND_EUTRAN_32 }, + { .num = 133, .band = MM_MODEM_BAND_EUTRAN_33 }, + { .num = 134, .band = MM_MODEM_BAND_EUTRAN_34 }, + { .num = 135, .band = MM_MODEM_BAND_EUTRAN_35 }, + { .num = 136, .band = MM_MODEM_BAND_EUTRAN_36 }, + { .num = 137, .band = MM_MODEM_BAND_EUTRAN_37 }, + { .num = 138, .band = MM_MODEM_BAND_EUTRAN_38 }, + { .num = 139, .band = MM_MODEM_BAND_EUTRAN_39 }, + { .num = 140, .band = MM_MODEM_BAND_EUTRAN_40 }, + { .num = 141, .band = MM_MODEM_BAND_EUTRAN_41 }, + { .num = 142, .band = MM_MODEM_BAND_EUTRAN_42 }, + { .num = 143, .band = MM_MODEM_BAND_EUTRAN_43 }, + { .num = 144, .band = MM_MODEM_BAND_EUTRAN_44 }, + { .num = 145, .band = MM_MODEM_BAND_EUTRAN_45 }, + { .num = 146, .band = MM_MODEM_BAND_EUTRAN_46 }, + { .num = 147, .band = MM_MODEM_BAND_EUTRAN_47 }, + { .num = 148, .band = MM_MODEM_BAND_EUTRAN_48 }, +}; + +static MMModemBand +uact_num_to_band (guint num) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (uact_band_config); i++) { + if (num == uact_band_config[i].num) + return uact_band_config[i].band; + } + return MM_MODEM_BAND_UNKNOWN; +} + +static guint +uact_band_to_num (MMModemBand band) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (uact_band_config); i++) { + if (band == uact_band_config[i].band) + return uact_band_config[i].num; + } + return 0; +} + +/*****************************************************************************/ +/* UACT? response parser */ + +static GArray * +uact_num_array_to_band_array (GArray *nums) +{ + GArray *bands = NULL; + guint i; + + if (!nums) + return NULL; + + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), nums->len); + for (i = 0; i < nums->len; i++) { + MMModemBand band; + + band = uact_num_to_band (g_array_index (nums, guint, i)); + g_array_append_val (bands, band); + } + + return bands; +} + +GArray * +mm_ublox_parse_uact_response (const gchar *response, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + GArray *nums = NULL; + GArray *bands = NULL; + + /* + * AT+UACT? + * +UACT: ,,,900,1800,1,8,101,103,107,108,120,138 + */ + r = g_regex_new ("\\+UACT: ([^,]*),([^,]*),([^,]*),(.*)(?:\\r\\n)?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + g_autofree gchar *bandstr = NULL; + + bandstr = mm_get_string_unquoted_from_match_info (match_info, 4); + nums = mm_parse_uint_list (bandstr, &inner_error); + } + + if (inner_error) { + g_propagate_error (error, inner_error); + return NULL; + } + + /* Convert to MMModemBand values */ + if (nums) { + bands = uact_num_array_to_band_array (nums); + g_array_unref (nums); + } + + if (!bands) + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No known band selection values matched in +UACT response: '%s'", response); + + return bands; +} + +/*****************************************************************************/ +/* UACT=? response parser */ + +static GArray * +parse_bands_from_string (const gchar *str, + const gchar *group, + gpointer log_object) +{ + GArray *bands = NULL; + GError *inner_error = NULL; + GArray *nums; + + nums = mm_parse_uint_list (str, &inner_error); + if (nums) { + gchar *tmpstr; + + bands = uact_num_array_to_band_array (nums); + tmpstr = mm_common_build_bands_string ((MMModemBand *)(gpointer)(bands->data), bands->len); + mm_obj_dbg (log_object, "modem reports support for %s bands: %s", group, tmpstr); + g_free (tmpstr); + + g_array_unref (nums); + } else if (inner_error) { + mm_obj_warn (log_object, "couldn't parse list of supported %s bands: %s", group, inner_error->message); + g_clear_error (&inner_error); + } + + return bands; +} + +gboolean +mm_ublox_parse_uact_test (const gchar *response, + gpointer log_object, + GArray **bands2g_out, + GArray **bands3g_out, + GArray **bands4g_out, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_auto(GStrv) split = NULL; + GError *inner_error = NULL; + const gchar *bands2g_str = NULL; + const gchar *bands3g_str = NULL; + const gchar *bands4g_str = NULL; + GArray *bands2g = NULL; + GArray *bands3g = NULL; + GArray *bands4g = NULL; + + g_assert (bands2g_out && bands3g_out && bands4g_out); + + /* + * AT+UACT=? + * +UACT: ,,,(900,1800),(1,8),(101,103,107,108,120),(138) + */ + r = g_regex_new ("\\+UACT: ([^,]*),([^,]*),([^,]*),(.*)(?:\\r\\n)?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (inner_error) + goto out; + + if (g_match_info_matches (match_info)) { + g_autofree gchar *aux = NULL; + guint n_groups; + + aux = mm_get_string_unquoted_from_match_info (match_info, 4); + split = mm_split_string_groups (aux); + n_groups = g_strv_length (split); + if (n_groups >= 1) + bands2g_str = split[0]; + if (n_groups >= 2) + bands3g_str = split[1]; + if (n_groups >= 3) + bands4g_str = split[2]; + } + + if (!bands2g_str && !bands3g_str && !bands4g_str) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "frequency groups not found: %s", response); + goto out; + } + + bands2g = parse_bands_from_string (bands2g_str, "2G", log_object); + bands3g = parse_bands_from_string (bands3g_str, "3G", log_object); + bands4g = parse_bands_from_string (bands4g_str, "4G", log_object); + + if (!bands2g->len && !bands3g->len && !bands4g->len) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "no supported frequencies reported: %s", response); + goto out; + } + + /* success */ + +out: + if (inner_error) { + if (bands2g) + g_array_unref (bands2g); + if (bands3g) + g_array_unref (bands3g); + if (bands4g) + g_array_unref (bands4g); + g_propagate_error (error, inner_error); + return FALSE; + } + + *bands2g_out = bands2g; + *bands3g_out = bands3g; + *bands4g_out = bands4g; + return TRUE; +} + +/*****************************************************************************/ +/* UACT=X command builder */ + +gchar * +mm_ublox_build_uact_set_command (GArray *bands, + GError **error) +{ + GString *command; + + /* Build command */ + command = g_string_new ("+UACT=,,,"); + + if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) + g_string_append (command, "0"); + else { + guint i; + + for (i = 0; i < bands->len; i++) { + MMModemBand band; + guint num; + + band = g_array_index (bands, MMModemBand, i); + num = uact_band_to_num (band); + if (!num) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Band unsupported by this plugin: %s", mm_modem_band_get_string (band)); + g_string_free (command, TRUE); + return NULL; + } + + g_string_append_printf (command, "%s%u", i == 0 ? "" : ",", num); + } + } + + return g_string_free (command, FALSE); +} + +/*****************************************************************************/ +/* URAT? response parser */ + +gboolean +mm_ublox_parse_urat_read_response (const gchar *response, + gpointer log_object, + MMModemMode *out_allowed, + MMModemMode *out_preferred, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + MMModemMode allowed = MM_MODEM_MODE_NONE; + MMModemMode preferred = MM_MODEM_MODE_NONE; + g_autofree gchar *allowed_str = NULL; + g_autofree gchar *preferred_str = NULL; + + g_assert (out_allowed != NULL && out_preferred != NULL); + + /* Response may be e.g.: + * +URAT: 1,2 + * +URAT: 1 + */ + r = g_regex_new ("\\+URAT: (\\d+)(?:,(\\d+))?(?:\\r\\n)?", 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + guint value = 0; + + /* Selected item is mandatory */ + if (!mm_get_uint_from_match_info (match_info, 1, &value)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't read AcT selected value"); + goto out; + } + if (value >= G_N_ELEMENTS (ublox_combinations)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unexpected AcT selected value: %u", value); + goto out; + } + allowed = ublox_combinations[value]; + allowed_str = mm_modem_mode_build_string_from_mask (allowed); + mm_obj_dbg (log_object, "current allowed modes retrieved: %s", allowed_str); + + /* Preferred item is optional */ + if (mm_get_uint_from_match_info (match_info, 2, &value)) { + if (value >= G_N_ELEMENTS (ublox_combinations)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unexpected AcT preferred value: %u", value); + goto out; + } + preferred = ublox_combinations[value]; + preferred_str = mm_modem_mode_build_string_from_mask (preferred); + mm_obj_dbg (log_object, "current preferred modes retrieved: %s", preferred_str); + if (mm_count_bits_set (preferred) != 1) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "AcT preferred value should be a single AcT: %s", preferred_str); + goto out; + } + if (!(allowed & preferred)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "AcT preferred value (%s) not a subset of the allowed value (%s)", + preferred_str, allowed_str); + goto out; + } + } + } + +out: + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (allowed == MM_MODEM_MODE_NONE) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse +URAT response: %s", response); + return FALSE; + } + + *out_allowed = allowed; + *out_preferred = preferred; + return TRUE; +} + +/*****************************************************************************/ +/* URAT=X command builder */ + +static gboolean +append_rat_value (GString *str, + MMModemMode mode, + GError **error) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (ublox_combinations); i++) { + if (ublox_combinations[i] == mode) { + g_string_append_printf (str, "%u", i); + return TRUE; + } + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No AcT value matches requested mode"); + return FALSE; +} + +gchar * +mm_ublox_build_urat_set_command (MMModemMode allowed, + MMModemMode preferred, + GError **error) +{ + GString *command; + + command = g_string_new ("+URAT="); + if (!append_rat_value (command, allowed, error)) { + g_string_free (command, TRUE); + return NULL; + } + + if (preferred != MM_MODEM_MODE_NONE) { + g_string_append (command, ","); + if (!append_rat_value (command, preferred, error)) { + g_string_free (command, TRUE); + return NULL; + } + } + + return g_string_free (command, FALSE); +} + +/*****************************************************************************/ +/* +UAUTHREQ=? test parser */ + +MMUbloxBearerAllowedAuth +mm_ublox_parse_uauthreq_test (const char *response, + gpointer log_object, + GError **error) +{ + MMUbloxBearerAllowedAuth mask = MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN; + GError *inner_error = NULL; + GArray *allowed_auths = NULL; + gchar **split; + guint split_len; + + /* + * Response may be like: + * AT+UAUTHREQ=? + * +UAUTHREQ: (1-4),(0-2),, + */ + response = mm_strip_tag (response, "+UAUTHREQ:"); + split = mm_split_string_groups (response); + split_len = g_strv_length (split); + if (split_len < 2) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unexpected number of groups in +UAUTHREQ=? response: %u", g_strv_length (split)); + goto out; + } + + allowed_auths = mm_parse_uint_list (split[1], &inner_error); + if (inner_error) + goto out; + + if (allowed_auths) { + guint i; + + for (i = 0; i < allowed_auths->len; i++) { + guint val; + + val = g_array_index (allowed_auths, guint, i); + switch (val) { + case 0: + mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_NONE; + break; + case 1: + mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_PAP; + break; + case 2: + mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP; + break; + case 3: + mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_AUTO; + break; + default: + mm_obj_warn (log_object, "unexpected +UAUTHREQ value: %u", val); + break; + } + } + } + + if (!mask) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No supported authentication methods in +UAUTHREQ=? response"); + goto out; + } + +out: + g_strfreev (split); + + if (allowed_auths) + g_array_unref (allowed_auths); + + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN; + } + + return mask; +} + +/*****************************************************************************/ +/* +UGCNTRD response parser */ + +gboolean +mm_ublox_parse_ugcntrd_response_for_cid (const gchar *response, + guint in_cid, + guint64 *out_session_tx_bytes, + guint64 *out_session_rx_bytes, + guint64 *out_total_tx_bytes, + guint64 *out_total_rx_bytes, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + guint64 session_tx_bytes = 0; + guint64 session_rx_bytes = 0; + guint64 total_tx_bytes = 0; + guint64 total_rx_bytes = 0; + gboolean matched = FALSE; + + /* Response may be e.g.: + * +UGCNTRD: 31,2704,1819,2724,1839 + * We assume only ONE line is returned. + */ + r = g_regex_new ("\\+UGCNTRD:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r != NULL); + + /* Report invalid CID given */ + if (!in_cid) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid CID given"); + goto out; + } + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + while (!inner_error && g_match_info_matches (match_info)) { + guint cid = 0; + + /* Matched CID? */ + if (!mm_get_uint_from_match_info (match_info, 1, &cid) || cid != in_cid) { + g_match_info_next (match_info, &inner_error); + continue; + } + + if (out_session_tx_bytes && !mm_get_u64_from_match_info (match_info, 2, &session_tx_bytes)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing session TX bytes"); + goto out; + } + + if (out_session_rx_bytes && !mm_get_u64_from_match_info (match_info, 3, &session_rx_bytes)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing session RX bytes"); + goto out; + } + + if (out_total_tx_bytes && !mm_get_u64_from_match_info (match_info, 4, &total_tx_bytes)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing total TX bytes"); + goto out; + } + + if (out_total_rx_bytes && !mm_get_u64_from_match_info (match_info, 5, &total_rx_bytes)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing total RX bytes"); + goto out; + } + + matched = TRUE; + break; + } + + if (!matched) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No statistics found for CID %u", in_cid); + goto out; + } + +out: + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (out_session_tx_bytes) + *out_session_tx_bytes = session_tx_bytes; + if (out_session_rx_bytes) + *out_session_rx_bytes = session_rx_bytes; + if (out_total_tx_bytes) + *out_total_tx_bytes = total_tx_bytes; + if (out_total_rx_bytes) + *out_total_rx_bytes = total_rx_bytes; + return TRUE; +} diff --git a/src/plugins/ublox/mm-modem-helpers-ublox.h b/src/plugins/ublox/mm-modem-helpers-ublox.h new file mode 100644 index 00000000..06bba003 --- /dev/null +++ b/src/plugins/ublox/mm-modem-helpers-ublox.h @@ -0,0 +1,213 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_MODEM_HELPERS_UBLOX_H +#define MM_MODEM_HELPERS_UBLOX_H + +#include <glib.h> +#include <ModemManager.h> + +/*****************************************************************************/ +/* AT Commands Support */ + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_SUPPORTED, + FEATURE_UNSUPPORTED, +} FeatureSupport; + +typedef enum { + SETTINGS_UPDATE_METHOD_UNKNOWN, + SETTINGS_UPDATE_METHOD_CFUN, + SETTINGS_UPDATE_METHOD_COPS, +} SettingsUpdateMethod; + +typedef struct UbloxSupportConfig { + gboolean loaded; + SettingsUpdateMethod method; + FeatureSupport uact; + FeatureSupport ubandsel; +} UbloxSupportConfig; + +/*****************************************************************************/ +/* +UPINCNT response parser */ + +gboolean mm_ublox_parse_upincnt_response (const gchar *response, + guint *out_pin_attempts, + guint *out_pin2_attempts, + guint *out_puk_attempts, + guint *out_puk2_attempts, + GError **error); + +/*****************************************************************************/ +/* UUSBCONF? response parser */ + +typedef enum { /*< underscore_name=mm_ublox_usb_profile >*/ + MM_UBLOX_USB_PROFILE_UNKNOWN, + MM_UBLOX_USB_PROFILE_RNDIS, + MM_UBLOX_USB_PROFILE_ECM, + MM_UBLOX_USB_PROFILE_BACK_COMPATIBLE, +} MMUbloxUsbProfile; + +gboolean mm_ublox_parse_uusbconf_response (const gchar *response, + MMUbloxUsbProfile *out_profile, + GError **error); + +/*****************************************************************************/ +/* UBMCONF? response parser */ + +typedef enum { /*< underscore_name=mm_ublox_networking_mode >*/ + MM_UBLOX_NETWORKING_MODE_UNKNOWN, + MM_UBLOX_NETWORKING_MODE_ROUTER, + MM_UBLOX_NETWORKING_MODE_BRIDGE, +} MMUbloxNetworkingMode; + +gboolean mm_ublox_parse_ubmconf_response (const gchar *response, + MMUbloxNetworkingMode *out_mode, + GError **error); + +/*****************************************************************************/ +/* UIPADDR=N response parser */ + +gboolean mm_ublox_parse_uipaddr_response (const gchar *response, + guint *out_cid, + gchar **out_if_name, + gchar **out_ipv4_address, + gchar **out_ipv4_subnet, + gchar **out_ipv6_global_address, + gchar **out_ipv6_link_local_address, + GError **error); + +/*****************************************************************************/ +/* CFUN? response parser */ + +gboolean mm_ublox_parse_cfun_response (const gchar *response, + MMModemPowerState *out_state, + GError **error); + +/*****************************************************************************/ +/* URAT=? response parser */ + +GArray *mm_ublox_parse_urat_test_response (const gchar *response, + gpointer log_object, + GError **error); + +/*****************************************************************************/ +/* Model-based config support loading */ + +gboolean mm_ublox_get_support_config (const gchar *model, + UbloxSupportConfig *config, + GError **error); + +/*****************************************************************************/ +/* Model-based supported modes filtering */ + +GArray *mm_ublox_filter_supported_modes (const gchar *model, + GArray *combinations, + gpointer logger, + GError **error); + +/*****************************************************************************/ +/* Model-based supported bands loading */ + +GArray *mm_ublox_get_supported_bands (const gchar *model, + gpointer log_object, + GError **error); + +/*****************************************************************************/ +/* UBANDSEL? response parser */ + +GArray *mm_ublox_parse_ubandsel_response (const gchar *response, + const gchar *model, + gpointer log_object, + GError **error); + +/*****************************************************************************/ +/* UBANDSEL=X command builder */ + +gchar *mm_ublox_build_ubandsel_set_command (GArray *bands, + const gchar *model, + GError **error); + +/*****************************************************************************/ +/* UACT? response parser */ + +GArray *mm_ublox_parse_uact_response (const gchar *response, + GError **error); + +/*****************************************************************************/ +/* UACT=? test parser */ + +gboolean mm_ublox_parse_uact_test (const gchar *response, + gpointer log_object, + GArray **bands_2g, + GArray **bands_3g, + GArray **bands_4g, + GError **error); + +/*****************************************************************************/ +/* UACT=X command builder */ + +gchar *mm_ublox_build_uact_set_command (GArray *bands, + GError **error); + +/*****************************************************************************/ +/* Get mode to apply when ANY */ + +MMModemMode mm_ublox_get_modem_mode_any (const GArray *combinations); + +/*****************************************************************************/ +/* URAT? response parser */ + +gboolean mm_ublox_parse_urat_read_response (const gchar *response, + gpointer log_object, + MMModemMode *out_allowed, + MMModemMode *out_preferred, + GError **error); + +/*****************************************************************************/ +/* URAT=X command builder */ + +gchar *mm_ublox_build_urat_set_command (MMModemMode allowed, + MMModemMode preferred, + GError **error); + +/*****************************************************************************/ +/* +UAUTHREQ=? test parser */ + +typedef enum { /*< underscore_name=mm_ublox_bearer_allowed_auth >*/ + MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN = 0, + MM_UBLOX_BEARER_ALLOWED_AUTH_NONE = 1 << 0, + MM_UBLOX_BEARER_ALLOWED_AUTH_PAP = 1 << 1, + MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP = 1 << 2, + MM_UBLOX_BEARER_ALLOWED_AUTH_AUTO = 1 << 3, +} MMUbloxBearerAllowedAuth; + +MMUbloxBearerAllowedAuth mm_ublox_parse_uauthreq_test (const char *response, + gpointer log_object, + GError **error); + +/*****************************************************************************/ +/* +UGCNTRD response parser */ + +gboolean mm_ublox_parse_ugcntrd_response_for_cid (const gchar *response, + guint in_cid, + guint64 *session_tx_bytes, + guint64 *session_rx_bytes, + guint64 *total_tx_bytes, + guint64 *total_rx_bytes, + GError **error); + +#endif /* MM_MODEM_HELPERS_UBLOX_H */ diff --git a/src/plugins/ublox/mm-plugin-ublox.c b/src/plugins/ublox/mm-plugin-ublox.c new file mode 100644 index 00000000..db6d94d3 --- /dev/null +++ b/src/plugins/ublox/mm-plugin-ublox.c @@ -0,0 +1,265 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-serial-parsers.h" +#include "mm-broadband-modem-ublox.h" +#include "mm-plugin-ublox.h" + +G_DEFINE_TYPE (MMPluginUblox, mm_plugin_ublox, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *sysfs_path, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_ublox_new (sysfs_path, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ +/* Custom init context */ + +typedef struct { + MMPortSerialAt *port; + GRegex *ready_regex; + guint timeout_id; + gint wait_timeout_secs; +} CustomInitContext; + +static void +custom_init_context_free (CustomInitContext *ctx) +{ + g_assert (!ctx->timeout_id); + g_regex_unref (ctx->ready_regex); + g_object_unref (ctx->port); + g_slice_free (CustomInitContext, ctx); +} + +static gboolean +ublox_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static gboolean +ready_timeout (GTask *task) +{ + CustomInitContext *ctx; + MMPortProbe *probe; + + ctx = g_task_get_task_data (task); + probe = g_task_get_source_object (task); + + ctx->timeout_id = 0; + + mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->ready_regex, + NULL, NULL, NULL); + + mm_obj_dbg (probe, "timed out waiting for READY unsolicited message"); + + /* not an error really, we didn't probe anything yet, that's all */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + + return G_SOURCE_REMOVE; +} + +static void +ready_received (MMPortSerialAt *port, + GMatchInfo *info, + GTask *task) +{ + CustomInitContext *ctx; + MMPortProbe *probe; + + ctx = g_task_get_task_data (task); + probe = g_task_get_source_object (task); + + g_source_remove (ctx->timeout_id); + ctx->timeout_id = 0; + + mm_obj_dbg (probe, "received READY: port is AT"); + + /* Flag as an AT port right away */ + mm_port_probe_set_result_at (probe, TRUE); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +wait_for_ready (GTask *task) +{ + CustomInitContext *ctx; + MMPortProbe *probe; + + ctx = g_task_get_task_data (task); + probe = g_task_get_source_object (task); + + mm_obj_dbg (probe, "waiting for READY unsolicited message..."); + + /* Configure a regex on the TTY, so that we stop the custom init + * as soon as +READY URC is received */ + mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, + ctx->ready_regex, + (MMPortSerialAtUnsolicitedMsgFn) ready_received, + task, + NULL); + + mm_obj_dbg (probe, "waiting %d seconds for init timeout", ctx->wait_timeout_secs); + + /* Otherwise, let the custom init timeout in some seconds. */ + ctx->timeout_id = g_timeout_add_seconds (ctx->wait_timeout_secs, (GSourceFunc) ready_timeout, task); +} + +static void +quick_at_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + MMPortProbe *probe; + g_autoptr(GError) error = NULL; + + probe = g_task_get_source_object (task); + + mm_port_serial_at_command_finish (port, res, &error); + if (error) { + /* On a timeout error, wait for READY URC */ + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { + wait_for_ready (task); + return; + } + /* On an unknown error, make it fatal */ + if (!mm_serial_parser_v1_is_known_error (error)) { + mm_obj_warn (probe, "custom port initialization logic failed: %s", error->message); + goto out; + } + } + + mm_obj_dbg (probe, "port is AT"); + mm_port_probe_set_result_at (probe, TRUE); + +out: + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +ublox_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + CustomInitContext *ctx; + gint wait_timeout_secs; + + task = g_task_new (probe, cancellable, callback, user_data); + + /* If no explicit READY_DELAY configured, we don't need a custom init procedure */ + wait_timeout_secs = mm_kernel_device_get_property_as_int (mm_port_probe_peek_port (probe), "ID_MM_UBLOX_PORT_READY_DELAY"); + if (wait_timeout_secs <= 0) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + ctx = g_slice_new0 (CustomInitContext); + ctx->wait_timeout_secs = wait_timeout_secs; + ctx->port = g_object_ref (port); + ctx->ready_regex = g_regex_new ("\\r\\n\\+AT:\\s*READY\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_task_set_task_data (task, ctx, (GDestroyNotify) custom_init_context_free); + + /* If the device hasn't been plugged in right away, we assume it was already + * running for some time. We validate the assumption with a quick AT probe, + * and if it times out, we run the explicit READY wait from scratch (e.g. + * to cope with the case where MM starts after the TTY has been exposed but + * where the device was also just reseted) */ + if (!mm_device_get_hotplugged (mm_port_probe_peek_device (probe))) { + mm_port_serial_at_command (ctx->port, + "AT", + 1, + FALSE, /* raw */ + FALSE, /* allow_cached */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)quick_at_ready, + task); + return; + } + + /* Device hotplugged and has a defined ready delay, wait for READY URC */ + wait_for_ready (task); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", NULL }; + static const guint16 vendor_ids[] = { 0x1546, 0 }; + static const gchar *vendor_strings[] = { "u-blox", NULL }; + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (ublox_custom_init), + .finish = G_CALLBACK (ublox_custom_init_finish), + }; + + return MM_PLUGIN (g_object_new (MM_TYPE_PLUGIN_UBLOX, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_SEND_DELAY, (guint64) 0, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + NULL)); +} + +static void +mm_plugin_ublox_init (MMPluginUblox *self) +{ +} + +static void +mm_plugin_ublox_class_init (MMPluginUbloxClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/ublox/mm-plugin-ublox.h b/src/plugins/ublox/mm-plugin-ublox.h new file mode 100644 index 00000000..adfc6247 --- /dev/null +++ b/src/plugins/ublox/mm-plugin-ublox.h @@ -0,0 +1,40 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_UBLOX_H +#define MM_PLUGIN_UBLOX_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_UBLOX (mm_plugin_ublox_get_type ()) +#define MM_PLUGIN_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_UBLOX, MMPluginUblox)) +#define MM_PLUGIN_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_UBLOX, MMPluginUbloxClass)) +#define MM_IS_PLUGIN_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_UBLOX)) +#define MM_IS_PLUGIN_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_UBLOX)) +#define MM_PLUGIN_UBLOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_UBLOX, MMPluginUbloxClass)) + +typedef struct { + MMPlugin parent; +} MMPluginUblox; + +typedef struct { + MMPluginClass parent; +} MMPluginUbloxClass; + +GType mm_plugin_ublox_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_UBLOX_H */ diff --git a/src/plugins/ublox/mm-sim-ublox.c b/src/plugins/ublox/mm-sim-ublox.c new file mode 100644 index 00000000..5850767e --- /dev/null +++ b/src/plugins/ublox/mm-sim-ublox.c @@ -0,0 +1,163 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-base-modem-at.h" + +#include "mm-sim-ublox.h" + +G_DEFINE_TYPE (MMSimUblox, mm_sim_ublox, MM_TYPE_BASE_SIM) + +/*****************************************************************************/ +/* SIM identifier loading */ + +static gchar * +load_sim_identifier_finish (MMBaseSim *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_sim_identifier_ready (MMSimUblox *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + gchar *simid; + + simid = MM_BASE_SIM_CLASS (mm_sim_ublox_parent_class)->load_sim_identifier_finish (MM_BASE_SIM (self), res, &error); + if (simid) + g_task_return_pointer (task, simid, g_free); + else + g_task_return_error (task, error); + g_object_unref (task); +} + +static void +ccid_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBaseSim *self; + const gchar *response; + gchar *parsed; + + response = mm_base_modem_at_command_finish (modem, res, NULL); + if (!response) + goto error; + + response = mm_strip_tag (response, "+CCID:"); + if (!response) + goto error; + + parsed = mm_3gpp_parse_iccid (response, NULL); + if (parsed) { + g_task_return_pointer (task, parsed, g_free); + g_object_unref (task); + return; + } + +error: + /* Chain up to parent method to for devices that don't support +CCID properly */ + self = g_task_get_source_object (task); + MM_BASE_SIM_CLASS (mm_sim_ublox_parent_class)->load_sim_identifier (self, + (GAsyncReadyCallback) parent_load_sim_identifier_ready, + task); +} + +static void +load_sim_identifier (MMBaseSim *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBaseModem *modem = NULL; + + g_object_get (self, + MM_BASE_SIM_MODEM, &modem, + NULL); + mm_base_modem_at_command ( + modem, + "+CCID", + 5, + FALSE, + (GAsyncReadyCallback)ccid_ready, + g_task_new (self, NULL, callback, user_data)); + g_object_unref (modem); +} + +/*****************************************************************************/ + +MMBaseSim * +mm_sim_ublox_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *sim; + + source = g_async_result_get_source_object (res); + sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!sim) + return NULL; + + /* Only export valid SIMs */ + mm_base_sim_export (MM_BASE_SIM (sim)); + + return MM_BASE_SIM (sim); +} + +void +mm_sim_ublox_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (MM_TYPE_SIM_UBLOX, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_SIM_MODEM, modem, + "active", TRUE, /* by default always active */ + NULL); +} + +static void +mm_sim_ublox_init (MMSimUblox *self) +{ +} + +static void +mm_sim_ublox_class_init (MMSimUbloxClass *klass) +{ + MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass); + + base_sim_class->load_sim_identifier = load_sim_identifier; + base_sim_class->load_sim_identifier_finish = load_sim_identifier_finish; +} diff --git a/src/plugins/ublox/mm-sim-ublox.h b/src/plugins/ublox/mm-sim-ublox.h new file mode 100644 index 00000000..31e2c98b --- /dev/null +++ b/src/plugins/ublox/mm-sim-ublox.h @@ -0,0 +1,51 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_SIM_UBLOX_H +#define MM_SIM_UBLOX_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-sim.h" + +#define MM_TYPE_SIM_UBLOX (mm_sim_ublox_get_type ()) +#define MM_SIM_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_UBLOX, MMSimUblox)) +#define MM_SIM_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_UBLOX, MMSimUbloxClass)) +#define MM_IS_SIM_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_UBLOX)) +#define MM_IS_SIM_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_UBLOX)) +#define MM_SIM_UBLOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_UBLOX, MMSimUbloxClass)) + +typedef struct _MMSimUblox MMSimUblox; +typedef struct _MMSimUbloxClass MMSimUbloxClass; + +struct _MMSimUblox { + MMBaseSim parent; +}; + +struct _MMSimUbloxClass { + MMBaseSimClass parent; +}; + +GType mm_sim_ublox_get_type (void); + +void mm_sim_ublox_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_sim_ublox_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_SIM_UBLOX_H */ diff --git a/src/plugins/ublox/tests/test-modem-helpers-ublox.c b/src/plugins/ublox/tests/test-modem-helpers-ublox.c new file mode 100644 index 00000000..2d662877 --- /dev/null +++ b/src/plugins/ublox/tests/test-modem-helpers-ublox.c @@ -0,0 +1,1026 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-ublox.h" + +#include "test-helpers.h" + +/*****************************************************************************/ +/* Test +UPINCNT responses */ + +typedef struct { + const gchar *str; + guint pin_attempts; + guint pin2_attempts; + guint puk_attempts; + guint puk2_attempts; +} UpinCntResponseTest; + +static const UpinCntResponseTest upincnt_response_tests[] = { + { .str = "+UPINCNT: 3,3,10,10\r\n", + .pin_attempts = 3, + .pin2_attempts = 3, + .puk_attempts = 10, + .puk2_attempts = 10 + }, + { .str = "+UPINCNT: 0,3,5,5\r\n", + .pin_attempts = 0, + .pin2_attempts = 3, + .puk_attempts = 5, + .puk2_attempts = 5 + }, + { .str = "+UPINCNT: 0,0,0,0\r\n", + .pin_attempts = 0, + .pin2_attempts = 0, + .puk_attempts = 0, + .puk2_attempts = 0 + }, +}; + +static void +test_upincnt_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (upincnt_response_tests); i++) { + GError *error = NULL; + gboolean success; + guint pin_attempts = G_MAXUINT; + guint pin2_attempts = G_MAXUINT; + guint puk_attempts = G_MAXUINT; + guint puk2_attempts = G_MAXUINT; + + success = mm_ublox_parse_upincnt_response (upincnt_response_tests[i].str, + &pin_attempts, &pin2_attempts, + &puk_attempts, &puk2_attempts, + &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (upincnt_response_tests[i].pin_attempts, ==, pin_attempts); + g_assert_cmpuint (upincnt_response_tests[i].pin2_attempts, ==, pin2_attempts); + g_assert_cmpuint (upincnt_response_tests[i].puk_attempts, ==, puk_attempts); + g_assert_cmpuint (upincnt_response_tests[i].puk2_attempts, ==, puk2_attempts); + } +} + +/*****************************************************************************/ +/* Test UUSBCONF? responses */ + +typedef struct { + const gchar *str; + MMUbloxUsbProfile profile; +} UusbconfResponseTest; + +static const UusbconfResponseTest uusbconf_response_tests[] = { + { + .str = "+UUSBCONF: 3,\"RNDIS\",,\"0x1146\"\r\n", + .profile = MM_UBLOX_USB_PROFILE_RNDIS + }, + { + .str = "+UUSBCONF: 2,\"ECM\",,\"0x1143\"\r\n", + .profile = MM_UBLOX_USB_PROFILE_ECM + }, + { + .str = "+UUSBCONF: 0,\"\",,\"0x1141\"\r\n", + .profile = MM_UBLOX_USB_PROFILE_BACK_COMPATIBLE + }, +}; + +static void +test_uusbconf_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (uusbconf_response_tests); i++) { + MMUbloxUsbProfile profile = MM_UBLOX_USB_PROFILE_UNKNOWN; + GError *error = NULL; + gboolean success; + + success = mm_ublox_parse_uusbconf_response (uusbconf_response_tests[i].str, &profile, &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (uusbconf_response_tests[i].profile, ==, profile); + } +} + +/*****************************************************************************/ +/* Test UBMCONF? responses */ + +typedef struct { + const gchar *str; + MMUbloxNetworkingMode mode; +} UbmconfResponseTest; + +static const UbmconfResponseTest ubmconf_response_tests[] = { + { + .str = "+UBMCONF: 1\r\n", + .mode = MM_UBLOX_NETWORKING_MODE_ROUTER + }, + { + .str = "+UBMCONF: 2\r\n", + .mode = MM_UBLOX_NETWORKING_MODE_BRIDGE + }, +}; + +static void +test_ubmconf_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (ubmconf_response_tests); i++) { + MMUbloxNetworkingMode mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN; + GError *error = NULL; + gboolean success; + + success = mm_ublox_parse_ubmconf_response (ubmconf_response_tests[i].str, &mode, &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (ubmconf_response_tests[i].mode, ==, mode); + } +} + +/*****************************************************************************/ +/* Test UIPADDR=N responses */ + +typedef struct { + const gchar *str; + guint cid; + const gchar *if_name; + const gchar *ipv4_address; + const gchar *ipv4_subnet; + const gchar *ipv6_global_address; + const gchar *ipv6_link_local_address; +} UipaddrResponseTest; + +static const UipaddrResponseTest uipaddr_response_tests[] = { + { + .str = "+UIPADDR: 1,\"ccinet0\",\"5.168.120.13\",\"255.255.255.0\",\"\",\"\"", + .cid = 1, + .if_name = "ccinet0", + .ipv4_address = "5.168.120.13", + .ipv4_subnet = "255.255.255.0", + }, + { + .str = "+UIPADDR: 2,\"ccinet1\",\"\",\"\",\"2001::1:200:FF:FE00:0/64\",\"FE80::200:FF:FE00:0/64\"", + .cid = 2, + .if_name = "ccinet1", + .ipv6_global_address = "2001::1:200:FF:FE00:0/64", + .ipv6_link_local_address = "FE80::200:FF:FE00:0/64", + }, + { + .str = "+UIPADDR: 3,\"ccinet2\",\"5.10.100.2\",\"255.255.255.0\",\"2001::1:200:FF:FE00:0/64\",\"FE80::200:FF:FE00:0/64\"", + .cid = 3, + .if_name = "ccinet2", + .ipv4_address = "5.10.100.2", + .ipv4_subnet = "255.255.255.0", + .ipv6_global_address = "2001::1:200:FF:FE00:0/64", + .ipv6_link_local_address = "FE80::200:FF:FE00:0/64", + }, +}; + +static void +test_uipaddr_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (uipaddr_response_tests); i++) { + GError *error = NULL; + gboolean success; + guint cid = G_MAXUINT; + gchar *if_name = NULL; + gchar *ipv4_address = NULL; + gchar *ipv4_subnet = NULL; + gchar *ipv6_global_address = NULL; + gchar *ipv6_link_local_address = NULL; + + success = mm_ublox_parse_uipaddr_response (uipaddr_response_tests[i].str, + &cid, + &if_name, + &ipv4_address, + &ipv4_subnet, + &ipv6_global_address, + &ipv6_link_local_address, + &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (uipaddr_response_tests[i].cid, ==, cid); + g_assert_cmpstr (uipaddr_response_tests[i].if_name, ==, if_name); + g_assert_cmpstr (uipaddr_response_tests[i].ipv4_address, ==, ipv4_address); + g_assert_cmpstr (uipaddr_response_tests[i].ipv4_subnet, ==, ipv4_subnet); + g_assert_cmpstr (uipaddr_response_tests[i].ipv6_global_address, ==, ipv6_global_address); + g_assert_cmpstr (uipaddr_response_tests[i].ipv6_link_local_address, ==, ipv6_link_local_address); + + g_free (if_name); + g_free (ipv4_address); + g_free (ipv4_subnet); + g_free (ipv6_global_address); + g_free (ipv6_link_local_address); + } +} + +/*****************************************************************************/ +/* Test CFUN? response */ + +typedef struct { + const gchar *str; + MMModemPowerState state; +} CfunQueryTest; + +static const CfunQueryTest cfun_query_tests[] = { + { "+CFUN: 1", MM_MODEM_POWER_STATE_ON }, + { "+CFUN: 1,0", MM_MODEM_POWER_STATE_ON }, + { "+CFUN: 0", MM_MODEM_POWER_STATE_LOW }, + { "+CFUN: 0,0", MM_MODEM_POWER_STATE_LOW }, + { "+CFUN: 4", MM_MODEM_POWER_STATE_LOW }, + { "+CFUN: 4,0", MM_MODEM_POWER_STATE_LOW }, + { "+CFUN: 19", MM_MODEM_POWER_STATE_LOW }, + { "+CFUN: 19,0", MM_MODEM_POWER_STATE_LOW }, +}; + +static void +test_cfun_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (cfun_query_tests); i++) { + GError *error = NULL; + gboolean success; + MMModemPowerState state = MM_MODEM_POWER_STATE_UNKNOWN; + + success = mm_ublox_parse_cfun_response (cfun_query_tests[i].str, &state, &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (cfun_query_tests[i].state, ==, state); + } +} + +/*****************************************************************************/ +/* Test URAT=? responses and model based filtering */ + +static void +compare_combinations (const gchar *response, + const gchar *model, + const MMModemModeCombination *expected_combinations, + guint n_expected_combinations) +{ + GArray *combinations; + GError *error = NULL; + guint i; + + combinations = mm_ublox_parse_urat_test_response (response, NULL, &error); + g_assert_no_error (error); + g_assert (combinations); + + combinations = mm_ublox_filter_supported_modes (model, combinations, NULL, &error); + g_assert_no_error (error); + g_assert (combinations); + + g_assert_cmpuint (combinations->len, ==, n_expected_combinations); + + for (i = 0; i < combinations->len; i++) { + MMModemModeCombination combination; + guint j; + gboolean found = FALSE; + + combination = g_array_index (combinations, MMModemModeCombination, i); + for (j = 0; !found && j < n_expected_combinations; j++) + found = (combination.allowed == expected_combinations[j].allowed && + combination.preferred == expected_combinations[j].preferred); + g_assert (found); + } + + g_array_unref (combinations); +} + +static void +test_urat_test_response_2g (void) +{ + static const MMModemModeCombination expected_combinations[] = { + { MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE } + }; + + compare_combinations ("+URAT: 0", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations)); + compare_combinations ("+URAT: 0,0", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations)); + compare_combinations ("+URAT: (0)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations)); + compare_combinations ("+URAT: (0),(0)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations)); +} + +static void +test_urat_test_response_2g3g (void) +{ + static const MMModemModeCombination expected_combinations[] = { + { MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_2G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G }, + }; + + compare_combinations ("+URAT: (0,1,2),(0,2)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations)); + compare_combinations ("+URAT: (0-2),(0,2)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations)); +} + +static void +test_urat_test_response_2g3g4g (void) +{ + static const MMModemModeCombination expected_combinations[] = { + { MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_2G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G }, + + { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_2G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G }, + + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G }, + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G }, + + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_2G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G }, + }; + + compare_combinations ("+URAT: (0,1,2,3,4,5,6),(0,2,3)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations)); + compare_combinations ("+URAT: (0-6),(0,2,3)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations)); +} + +static void +test_mode_filtering_toby_l201 (void) +{ + static const MMModemModeCombination expected_combinations[] = { + { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G }, + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G }, + }; + + compare_combinations ("+URAT: (0-6),(0,2,3)", "TOBY-L201", expected_combinations, G_N_ELEMENTS (expected_combinations)); +} + +static void +test_mode_filtering_lisa_u200 (void) +{ + static const MMModemModeCombination expected_combinations[] = { + { MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE }, + + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_2G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G }, + }; + + compare_combinations ("+URAT: (0-6),(0,2,3)", "LISA-U200", expected_combinations, G_N_ELEMENTS (expected_combinations)); +} + +static void +test_mode_filtering_sara_u280 (void) +{ + static const MMModemModeCombination expected_combinations[] = { + { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE }, + }; + + compare_combinations ("+URAT: (0-6),(0,2,3)", "SARA-U280", expected_combinations, G_N_ELEMENTS (expected_combinations)); +} + +/*****************************************************************************/ +/* URAT? response parser and URAT=X command builder */ + +typedef struct { + const gchar *command; + const gchar *response; + MMModemMode allowed; + MMModemMode preferred; +} UratTest; + +static const UratTest urat_tests[] = { + { + .command = "+URAT=1,2", + .response = "+URAT: 1,2\r\n", + .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G), + .preferred = MM_MODEM_MODE_3G, + }, + { + .command = "+URAT=4,0", + .response = "+URAT: 4,0\r\n", + .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G), + .preferred = MM_MODEM_MODE_2G, + }, + { + .command = "+URAT=0", + .response = "+URAT: 0\r\n", + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE, + }, + { + .command = "+URAT=6", + .response = "+URAT: 6\r\n", + .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G), + .preferred = MM_MODEM_MODE_NONE, + }, +}; + +static void +test_urat_read_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (urat_tests); i++) { + MMModemMode allowed = MM_MODEM_MODE_NONE; + MMModemMode preferred = MM_MODEM_MODE_NONE; + GError *error = NULL; + gboolean success; + + success = mm_ublox_parse_urat_read_response (urat_tests[i].response, NULL, + &allowed, &preferred, &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (urat_tests[i].allowed, ==, allowed); + g_assert_cmpuint (urat_tests[i].preferred, ==, preferred); + } +} + +static void +test_urat_write_command (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (urat_tests); i++) { + gchar *command; + GError *error = NULL; + + command = mm_ublox_build_urat_set_command (urat_tests[i].allowed, urat_tests[i].preferred, &error); + g_assert_no_error (error); + g_assert_cmpstr (command, ==, urat_tests[i].command); + g_free (command); + } +} + +/*****************************************************************************/ +/* Test +UBANDSEL? response parser */ + +static void +common_validate_ubandsel_response (const gchar *str, + const MMModemBand *expected_bands, + const gchar *model, + guint n_expected_bands) +{ + GError *error = NULL; + GArray *bands; + + bands = mm_ublox_parse_ubandsel_response (str, model, NULL, &error); + g_assert_no_error (error); + g_assert (bands); + + mm_test_helpers_compare_bands (bands, expected_bands, n_expected_bands); + g_array_unref (bands); +} + +static void +test_ubandsel_response_four (void) +{ + const MMModemBand expected_bands[] = { + /* 700 */ MM_MODEM_BAND_EUTRAN_4, + /* 1700 */ MM_MODEM_BAND_EUTRAN_13 + }; + + common_validate_ubandsel_response ("+UBANDSEL: 700,1700\r\n", expected_bands, "LARA-R204", G_N_ELEMENTS (expected_bands)); +} + +static void +test_ubandsel_response_three (void) +{ + const MMModemBand expected_bands[] = { + /* 800 */ MM_MODEM_BAND_UTRAN_6, + /* 850 */ MM_MODEM_BAND_G850, MM_MODEM_BAND_UTRAN_5, + /* 900 */ MM_MODEM_BAND_EGSM, MM_MODEM_BAND_UTRAN_8, + /* 1900 */ MM_MODEM_BAND_PCS, MM_MODEM_BAND_UTRAN_2, + /* 2100 */ MM_MODEM_BAND_UTRAN_1 + }; + + common_validate_ubandsel_response ("+UBANDSEL: 800,850,900,1900,2100\r\n", expected_bands, "SARA-U201", G_N_ELEMENTS (expected_bands)); +} + +static void +test_ubandsel_response_two (void) +{ + const MMModemBand expected_bands[] = { + /* 850 */ MM_MODEM_BAND_G850, + /* 900 */ MM_MODEM_BAND_EGSM, + /* 1800 */ MM_MODEM_BAND_DCS, + /* 1900 */ MM_MODEM_BAND_PCS + }; + + common_validate_ubandsel_response ("+UBANDSEL: 850,900,1800,1900\r\n", expected_bands, "SARA-G310", G_N_ELEMENTS (expected_bands)); +} + +static void +test_ubandsel_response_one (void) +{ + const MMModemBand expected_bands[] = { + /* 850 */ MM_MODEM_BAND_G850, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_EUTRAN_5, + /* 1700 */ MM_MODEM_BAND_EUTRAN_4, + /* 1900 */ MM_MODEM_BAND_PCS, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_EUTRAN_2 + }; + + common_validate_ubandsel_response ("+UBANDSEL: 850,1700,1900\r\n", expected_bands, "TOBY-R200", G_N_ELEMENTS (expected_bands)); +} + +/*****************************************************************************/ +/* +UBANDSEL=x command builder */ + +static void +common_validate_ubandsel_request (const MMModemBand *bands, + guint n_bands, + const gchar *model, + const gchar *expected_request) +{ + GError *error = NULL; + GArray *bands_array; + gchar *request; + + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_bands); + g_array_append_vals (bands_array, bands, n_bands); + + request = mm_ublox_build_ubandsel_set_command (bands_array, model, &error); + g_assert_no_error (error); + g_assert (request); + + g_assert_cmpstr (request, ==, expected_request); + + g_array_unref (bands_array); + g_free (request); +} + +static void +test_ubandsel_request_any (void) +{ + const MMModemBand bands[] = { + MM_MODEM_BAND_ANY + }; + + common_validate_ubandsel_request (bands, G_N_ELEMENTS (bands), "TOBY-R200", "+UBANDSEL=0"); +} + +static void +test_ubandsel_request_2g (void) +{ + const MMModemBand bands[] = { + MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS + }; + + common_validate_ubandsel_request (bands, G_N_ELEMENTS (bands), "SARA-G310", "+UBANDSEL=850,900,1800,1900"); +} + +static void +test_ubandsel_request_1800 (void) +{ + const MMModemBand bands[] = { + MM_MODEM_BAND_DCS, MM_MODEM_BAND_UTRAN_3, MM_MODEM_BAND_EUTRAN_3 + }; + + common_validate_ubandsel_request (bands, G_N_ELEMENTS (bands), "TOBY-R200", "+UBANDSEL=1800"); +} + +/*****************************************************************************/ +/* Test +UACT? response parser */ + +static void +common_validate_uact_response (const gchar *str, + const MMModemBand *expected_bands, + guint n_expected_bands) +{ + GError *error = NULL; + GArray *bands; + + bands = mm_ublox_parse_uact_response (str, &error); + + if (n_expected_bands > 0) { + g_assert (bands); + g_assert_no_error (error); + mm_test_helpers_compare_bands (bands, expected_bands, n_expected_bands); + g_array_unref (bands); + } else { + g_assert (!bands); + g_assert (error); + g_error_free (error); + } +} + +static void +test_uact_response_empty_list (void) +{ + common_validate_uact_response ("", NULL, 0); + common_validate_uact_response ("+UACT: ,,,\r\n", NULL, 0); +} + +static void +test_uact_response_2g (void) +{ + const MMModemBand expected_bands[] = { + MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS, + }; + + common_validate_uact_response ("+UACT: ,,,900,1800,1900,850\r\n", + expected_bands, G_N_ELEMENTS (expected_bands)); +} + +static void +test_uact_response_2g3g (void) +{ + const MMModemBand expected_bands[] = { + MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS, + MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_3, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_7, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_9, + }; + + common_validate_uact_response ("+UACT: ,,,900,1800,1900,850,1,2,3,4,5,6,7,8,9\r\n", + expected_bands, G_N_ELEMENTS (expected_bands)); +} + +static void +test_uact_response_2g3g4g (void) +{ + const MMModemBand expected_bands[] = { + MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS, + MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_3, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_7, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_9, + MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_6, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_9, + }; + + common_validate_uact_response ("+UACT: ,,,900,1800,1900,850,1,2,3,4,5,6,7,8,9,101,102,103,104,105,106,107,108,109\r\n", + expected_bands, G_N_ELEMENTS (expected_bands)); +} + +/*****************************************************************************/ +/* Test +UACT=? test parser */ + +static void +common_validate_uact_test (const gchar *str, + const MMModemBand *expected_bands_2g, + guint n_expected_bands_2g, + const MMModemBand *expected_bands_3g, + guint n_expected_bands_3g, + const MMModemBand *expected_bands_4g, + guint n_expected_bands_4g) +{ + GError *error = NULL; + gboolean result; + GArray *bands_2g = NULL; + GArray *bands_3g = NULL; + GArray *bands_4g = NULL; + + result = mm_ublox_parse_uact_test (str, NULL, &bands_2g, &bands_3g, &bands_4g, &error); + g_assert_no_error (error); + g_assert (result); + + mm_test_helpers_compare_bands (bands_2g, expected_bands_2g, n_expected_bands_2g); + if (bands_2g) + g_array_unref (bands_2g); + mm_test_helpers_compare_bands (bands_3g, expected_bands_3g, n_expected_bands_3g); + if (bands_3g) + g_array_unref (bands_3g); + mm_test_helpers_compare_bands (bands_4g, expected_bands_4g, n_expected_bands_4g); + if (bands_4g) + g_array_unref (bands_4g); +} + +static void +test_uact_test_2g (void) +{ + const MMModemBand expected_bands_2g[] = { + MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS + }; + + common_validate_uact_test ("+UACT: ,,,(900,1800)\r\n", + expected_bands_2g, G_N_ELEMENTS (expected_bands_2g), + NULL, 0, + NULL, 0); +} + +static void +test_uact_test_2g3g (void) +{ + const MMModemBand expected_bands_2g[] = { + MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS + }; + const MMModemBand expected_bands_3g[] = { + MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8 + }; + + common_validate_uact_test ("+UACT: ,,,(900,1800),(1,8)\r\n", + expected_bands_2g, G_N_ELEMENTS (expected_bands_2g), + expected_bands_3g, G_N_ELEMENTS (expected_bands_3g), + NULL, 0); +} + +static void +test_uact_test_2g3g4g (void) +{ + const MMModemBand expected_bands_2g[] = { + MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS + }; + const MMModemBand expected_bands_3g[] = { + MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8 + }; + const MMModemBand expected_bands_4g[] = { + MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 + }; + + common_validate_uact_test ("+UACT: ,,,(900,1800),(1,8),(101,103,107,108,120)\r\n", + expected_bands_2g, G_N_ELEMENTS (expected_bands_2g), + expected_bands_3g, G_N_ELEMENTS (expected_bands_3g), + expected_bands_4g, G_N_ELEMENTS (expected_bands_4g)); +} + +static void +test_uact_test_2g3g4g_2 (void) +{ + const MMModemBand expected_bands_2g[] = { + MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS + }; + const MMModemBand expected_bands_3g[] = { + MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8 + }; + const MMModemBand expected_bands_4g[] = { + MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 + }; + + common_validate_uact_test ("+UACT: ,,,(900,1800),(1,8),(101,103,107,108,120),(138)\r\n", + expected_bands_2g, G_N_ELEMENTS (expected_bands_2g), + expected_bands_3g, G_N_ELEMENTS (expected_bands_3g), + expected_bands_4g, G_N_ELEMENTS (expected_bands_4g)); +} + +/*****************************************************************************/ +/* +UACT=x command builder */ + +static void +common_validate_uact_request (const MMModemBand *bands, + guint n_bands, + const gchar *expected_request) +{ + GError *error = NULL; + GArray *bands_array; + gchar *request; + + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_bands); + g_array_append_vals (bands_array, bands, n_bands); + + request = mm_ublox_build_uact_set_command (bands_array, &error); + g_assert_no_error (error); + g_assert (request); + + g_assert_cmpstr (request, ==, expected_request); + + g_array_unref (bands_array); + g_free (request); +} + +static void +test_uact_request_any (void) +{ + const MMModemBand bands[] = { + MM_MODEM_BAND_ANY + }; + + common_validate_uact_request (bands, G_N_ELEMENTS (bands), "+UACT=,,,0"); +} + +static void +test_uact_request_2g (void) +{ + const MMModemBand bands[] = { + MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, + }; + + common_validate_uact_request (bands, G_N_ELEMENTS (bands), "+UACT=,,,900,1800"); +} + +static void +test_uact_request_3g (void) +{ + const MMModemBand bands[] = { + MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8, + }; + + common_validate_uact_request (bands, G_N_ELEMENTS (bands), "+UACT=,,,1,8"); +} + +static void +test_uact_request_4g (void) +{ + const MMModemBand bands[] = { + MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 + }; + + common_validate_uact_request (bands, G_N_ELEMENTS (bands), "+UACT=,,,101,103,107,108,120"); +} + +/*****************************************************************************/ +/* Test +UAUTHREQ=? responses */ + +static void +common_validate_uauthreq_test (const gchar *str, + MMUbloxBearerAllowedAuth expected_allowed_auths) +{ + GError *error = NULL; + MMUbloxBearerAllowedAuth allowed_auths; + + allowed_auths = mm_ublox_parse_uauthreq_test (str, NULL, &error); + g_assert_no_error (error); + g_assert_cmpuint (allowed_auths, ==, expected_allowed_auths); +} + +static void +test_uauthreq_tobyl4 (void) +{ + common_validate_uauthreq_test ("+UAUTHREQ: (1-4),(0-2),,", + (MM_UBLOX_BEARER_ALLOWED_AUTH_NONE | + MM_UBLOX_BEARER_ALLOWED_AUTH_PAP | + MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP)); +} + +static void +test_uauthreq_with_auto (void) +{ + common_validate_uauthreq_test ("+UAUTHREQ: (1-4),(0-3),,", + (MM_UBLOX_BEARER_ALLOWED_AUTH_NONE | + MM_UBLOX_BEARER_ALLOWED_AUTH_PAP | + MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP | + MM_UBLOX_BEARER_ALLOWED_AUTH_AUTO)); +} + +static void +test_uauthreq_less_fields (void) +{ + common_validate_uauthreq_test ("+UAUTHREQ: (1-4),(0-2)", + (MM_UBLOX_BEARER_ALLOWED_AUTH_NONE | + MM_UBLOX_BEARER_ALLOWED_AUTH_PAP | + MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP)); +} + +/*****************************************************************************/ +/* Test +UGCNTRD responses */ + +typedef struct { + const gchar *str; + guint cid; + guint64 session_tx_bytes; + guint64 session_rx_bytes; + guint64 total_tx_bytes; + guint64 total_rx_bytes; +} UgcntrdResponseTest; + +static const UgcntrdResponseTest ugcntrd_response_tests[] = { + { + .str = "+UGCNTRD: 1, 100, 0, 100, 0", + .cid = 1, + .session_tx_bytes = 100, + .session_rx_bytes = 0, + .total_tx_bytes = 100, + .total_rx_bytes = 0 + }, + { + .str = "+UGCNTRD: 31,2704,1819,2724,1839", + .cid = 31, + .session_tx_bytes = 2704, + .session_rx_bytes = 1819, + .total_tx_bytes = 2724, + .total_rx_bytes = 1839 + }, + { + .str = "+UGCNTRD: 1, 100, 0, 100, 0\r\n" + "+UGCNTRD: 31,2704,1819,2724,1839\r\n", + .cid = 1, + .session_tx_bytes = 100, + .session_rx_bytes = 0, + .total_tx_bytes = 100, + .total_rx_bytes = 0 + }, + { + .str = "+UGCNTRD: 1, 100, 0, 100, 0\r\n" + "+UGCNTRD: 31,2704,1819,2724,1839\r\n", + .cid = 31, + .session_tx_bytes = 2704, + .session_rx_bytes = 1819, + .total_tx_bytes = 2724, + .total_rx_bytes = 1839 + }, + { + .str = "+UGCNTRD: 2,1397316870,113728263578,1397316870,113728263578\r\n", + .cid = 2, + .session_tx_bytes = 1397316870ULL, + .session_rx_bytes = 113728263578ULL, + .total_tx_bytes = 1397316870ULL, + .total_rx_bytes = 113728263578ULL + } +}; + +static void +test_ugcntrd_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (ugcntrd_response_tests); i++) { + GError *error = NULL; + gboolean success; + guint64 session_tx_bytes = 0; + guint64 session_rx_bytes = 0; + guint64 total_tx_bytes = 0; + guint64 total_rx_bytes = 0; + + success = mm_ublox_parse_ugcntrd_response_for_cid (ugcntrd_response_tests[i].str, + ugcntrd_response_tests[i].cid, + &session_tx_bytes, + &session_rx_bytes, + &total_tx_bytes, + &total_rx_bytes, + &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (ugcntrd_response_tests[i].session_tx_bytes, ==, session_tx_bytes); + g_assert_cmpuint (ugcntrd_response_tests[i].session_rx_bytes, ==, session_rx_bytes); + g_assert_cmpuint (ugcntrd_response_tests[i].total_tx_bytes, ==, total_tx_bytes); + g_assert_cmpuint (ugcntrd_response_tests[i].total_rx_bytes, ==, total_rx_bytes); + } +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/ublox/upincnt/response", test_upincnt_response); + g_test_add_func ("/MM/ublox/uusbconf/response", test_uusbconf_response); + g_test_add_func ("/MM/ublox/ubmconf/response", test_ubmconf_response); + g_test_add_func ("/MM/ublox/uipaddr/response", test_uipaddr_response); + g_test_add_func ("/MM/ublox/cfun/response", test_cfun_response); + g_test_add_func ("/MM/ublox/urat/test/response/2g", test_urat_test_response_2g); + g_test_add_func ("/MM/ublox/urat/test/response/2g3g", test_urat_test_response_2g3g); + g_test_add_func ("/MM/ublox/urat/test/response/2g3g4g", test_urat_test_response_2g3g4g); + g_test_add_func ("/MM/ublox/urat/test/response/toby-l201", test_mode_filtering_toby_l201); + g_test_add_func ("/MM/ublox/urat/test/response/lisa-u200", test_mode_filtering_lisa_u200); + g_test_add_func ("/MM/ublox/urat/test/response/sara-u280", test_mode_filtering_sara_u280); + g_test_add_func ("/MM/ublox/urat/read/response", test_urat_read_response); + g_test_add_func ("/MM/ublox/urat/write/command", test_urat_write_command); + g_test_add_func ("/MM/ublox/ubandsel/response/one", test_ubandsel_response_one); + g_test_add_func ("/MM/ublox/ubandsel/response/two", test_ubandsel_response_two); + g_test_add_func ("/MM/ublox/ubandsel/response/three", test_ubandsel_response_three); + g_test_add_func ("/MM/ublox/ubandsel/response/four", test_ubandsel_response_four); + g_test_add_func ("/MM/ublox/ubandsel/request/any", test_ubandsel_request_any); + g_test_add_func ("/MM/ublox/ubandsel/request/2g", test_ubandsel_request_2g); + g_test_add_func ("/MM/ublox/ubandsel/request/1800", test_ubandsel_request_1800); + g_test_add_func ("/MM/ublox/uact/response/empty-list", test_uact_response_empty_list); + g_test_add_func ("/MM/ublox/uact/response/2g", test_uact_response_2g); + g_test_add_func ("/MM/ublox/uact/response/2g3g", test_uact_response_2g3g); + g_test_add_func ("/MM/ublox/uact/response/2g3g4g", test_uact_response_2g3g4g); + g_test_add_func ("/MM/ublox/uact/test/2g", test_uact_test_2g); + g_test_add_func ("/MM/ublox/uact/test/2g3g", test_uact_test_2g3g); + g_test_add_func ("/MM/ublox/uact/test/2g3g4g", test_uact_test_2g3g4g); + g_test_add_func ("/MM/ublox/uact/test/2g3g4g/2", test_uact_test_2g3g4g_2); + g_test_add_func ("/MM/ublox/uact/request/any", test_uact_request_any); + g_test_add_func ("/MM/ublox/uact/request/2g", test_uact_request_2g); + g_test_add_func ("/MM/ublox/uact/request/3g", test_uact_request_3g); + g_test_add_func ("/MM/ublox/uact/request/4g", test_uact_request_4g); + g_test_add_func ("/MM/ublox/uauthreq/test/tobyl4", test_uauthreq_tobyl4); + g_test_add_func ("/MM/ublox/uauthreq/test/with-auto", test_uauthreq_with_auto); + g_test_add_func ("/MM/ublox/uauthreq/test/less-fields", test_uauthreq_less_fields); + g_test_add_func ("/MM/ublox/ugcntrd/response", test_ugcntrd_response); + + return g_test_run (); +} diff --git a/src/plugins/via/mm-broadband-modem-via.c b/src/plugins/via/mm-broadband-modem-via.c new file mode 100644 index 00000000..896db8cd --- /dev/null +++ b/src/plugins/via/mm-broadband-modem-via.c @@ -0,0 +1,543 @@ +/* -*- 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) 2012 Red Hat, Inc. + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-errors-types.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-via.h" +#include "mm-iface-modem-cdma.h" +#include "mm-iface-modem.h" + +static void iface_modem_cdma_init (MMIfaceModemCdma *iface); + +static MMIfaceModemCdma *iface_modem_cdma_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemVia, mm_broadband_modem_via, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init)) + +struct _MMBroadbandModemViaPrivate { + /* Regex for signal quality related notifications */ + GRegex *hrssilvl_regex; /* EVDO signal strength */ + + /* Regex for other notifications to ignore */ + GRegex *mode_regex; /* Access technology change */ + GRegex *dosession_regex; /* EVDO data dormancy */ + GRegex *simst_regex; + GRegex *vpon_regex; + GRegex *creg_regex; + GRegex *vrom_regex; /* Roaming indicator (reportedly unreliable) */ + GRegex *vser_regex; + GRegex *ciev_regex; + GRegex *vpup_regex; +}; + +/*****************************************************************************/ +/* Setup registration checks (CDMA interface) */ + +typedef struct { + gboolean skip_qcdm_call_manager_step; + gboolean skip_qcdm_hdr_step; + gboolean skip_at_cdma_service_status_step; + gboolean skip_at_cdma1x_serving_system_step; + gboolean skip_detailed_registration_state; +} SetupRegistrationChecksResults; + +static gboolean +setup_registration_checks_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + gboolean *skip_qcdm_call_manager_step, + gboolean *skip_qcdm_hdr_step, + gboolean *skip_at_cdma_service_status_step, + gboolean *skip_at_cdma1x_serving_system_step, + gboolean *skip_detailed_registration_state, + GError **error) +{ + SetupRegistrationChecksResults *results; + + results = g_task_propagate_pointer (G_TASK (res), error); + if (!results) + return FALSE; + + *skip_qcdm_call_manager_step = results->skip_qcdm_call_manager_step; + *skip_qcdm_hdr_step = results->skip_qcdm_hdr_step; + *skip_at_cdma_service_status_step = results->skip_at_cdma_service_status_step; + *skip_at_cdma1x_serving_system_step = results->skip_at_cdma1x_serving_system_step; + *skip_detailed_registration_state = results->skip_detailed_registration_state; + + g_free (results); + + return TRUE; +} + +static void +parent_setup_registration_checks_ready (MMIfaceModemCdma *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + SetupRegistrationChecksResults *results; + + results = g_new0 (SetupRegistrationChecksResults, 1); + + if (!iface_modem_cdma_parent->setup_registration_checks_finish (self, + res, + &results->skip_qcdm_call_manager_step, + &results->skip_qcdm_hdr_step, + &results->skip_at_cdma_service_status_step, + &results->skip_at_cdma1x_serving_system_step, + &results->skip_detailed_registration_state, + &error)) { + g_free (results); + g_task_return_error (task, error); + } else { + /* Skip +CSS */ + results->skip_at_cdma1x_serving_system_step = TRUE; + /* Skip +CAD */ + results->skip_at_cdma_service_status_step = TRUE; + /* Force to always use the detailed registration checks, as we have + * ^SYSINFO for that */ + results->skip_detailed_registration_state = FALSE; + g_task_return_pointer (task, results, g_free); + } + g_object_unref (task); +} + +static void +setup_registration_checks (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's checks first */ + iface_modem_cdma_parent->setup_registration_checks ( + self, + (GAsyncReadyCallback)parent_setup_registration_checks_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Detailed registration state (CDMA interface) */ + +typedef struct { + MMModemCdmaRegistrationState detailed_cdma1x_state; + MMModemCdmaRegistrationState detailed_evdo_state; +} DetailedRegistrationStateResults; + +static gboolean +get_detailed_registration_state_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + MMModemCdmaRegistrationState *detailed_cdma1x_state, + MMModemCdmaRegistrationState *detailed_evdo_state, + GError **error) +{ + g_autofree DetailedRegistrationStateResults *results = NULL; + + results = g_task_propagate_pointer (G_TASK (res), error); + if (!results) + return FALSE; + + *detailed_cdma1x_state = results->detailed_cdma1x_state; + *detailed_evdo_state = results->detailed_evdo_state; + return TRUE; +} + +static void +sysinfo_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) + +{ + DetailedRegistrationStateResults *ctx; + g_autofree DetailedRegistrationStateResults *results = NULL; + const gchar *response; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + MMModemCdmaRegistrationState reg_state; + guint val = 0; + + ctx = g_task_get_task_data (task); + + /* Set input detailed states as fallback */ + results = g_memdup (ctx, sizeof (*ctx)); + + /* If error, leave superclass' reg state alone if AT^SYSINFO isn't supported. */ + response = mm_base_modem_at_command_finish (self, res, NULL); + if (!response) + goto out; + + response = mm_strip_tag (response, "^SYSINFO:"); + + /* Format is "<srv_status>,<srv_domain>,<roam_status>,<sys_mode>,<sim_state>" */ + r = g_regex_new ("\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (r != NULL); + + /* Try to parse the results */ + g_regex_match (r, response, 0, &match_info); + if (g_match_info_get_match_count (match_info) < 6) { + mm_obj_warn (self, "failed to parse ^SYSINFO response: '%s'", response); + goto out; + } + + /* At this point the generic code already knows we've been registered */ + reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + + if (mm_get_uint_from_match_info (match_info, 1, &val)) { + if (val == 2) { + /* Service available, check roaming state */ + val = 0; + if (mm_get_uint_from_match_info (match_info, 3, &val)) { + if (val == 0) + reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME; + else if (val == 1) + reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING; + } + } + } + + /* Check service type */ + val = 0; + if (mm_get_uint_from_match_info (match_info, 4, &val)) { + if (val == 2) /* CDMA */ + results->detailed_cdma1x_state = reg_state; + else if (val == 4) /* HDR */ + results->detailed_evdo_state = reg_state; + else if (val == 8) { /* Hybrid */ + results->detailed_cdma1x_state = reg_state; + results->detailed_evdo_state = reg_state; + } + } else { + /* Say we're registered to something even though sysmode parsing failed */ + mm_obj_dbg (self, "SYSMODE parsing failed: assuming registered at least in CDMA1x"); + results->detailed_cdma1x_state = reg_state; + } + +out: + g_task_return_pointer (task, g_steal_pointer (&results), g_free); + g_object_unref (task); +} + +static void +get_detailed_registration_state (MMIfaceModemCdma *self, + MMModemCdmaRegistrationState cdma1x_state, + MMModemCdmaRegistrationState evdo_state, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + DetailedRegistrationStateResults *ctx; + + /* Setup context */ + ctx = g_new0 (DetailedRegistrationStateResults, 1); + ctx->detailed_cdma1x_state = cdma1x_state; + ctx->detailed_evdo_state = evdo_state; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SYSINFO", + 3, + FALSE, + (GAsyncReadyCallback)sysinfo_ready, + task); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (CDMA interface) */ + +static void +handle_evdo_quality_change (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemVia *self) +{ + guint quality = 0; + + if (mm_get_uint_from_match_info (match_info, 1, &quality)) { + quality = MM_CLAMP_HIGH (quality, 100); + mm_obj_dbg (self, "EVDO signal quality: %u", quality); + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); + } +} + +static void +set_unsolicited_events_handlers (MMBroadbandModemVia *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* Signal quality related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->hrssilvl_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)handle_evdo_quality_change : NULL, + enable ? self : NULL, + NULL); + } +} + +static gboolean +modem_cdma_setup_cleanup_unsolicited_events_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_cdma_setup_unsolicited_events_ready (MMIfaceModemCdma *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_cdma_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_VIA (self), TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_cdma_setup_unsolicited_events (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_cdma_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cdma_setup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static void +parent_cdma_cleanup_unsolicited_events_ready (MMIfaceModemCdma *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_cdma_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +modem_cdma_cleanup_unsolicited_events (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own cleanup first */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_VIA (self), FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_cdma_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cdma_cleanup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +set_ignored_unsolicited_events_handlers (MMBroadbandModemVia *self) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->mode_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->dosession_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->simst_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->vpon_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->creg_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->vrom_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->vser_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->ciev_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->vpup_regex, + NULL, NULL, NULL); + } +} + +static void +setup_ports (MMBroadbandModem *self) +{ + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_via_parent_class)->setup_ports (self); + + /* Unsolicited messages to always ignore */ + set_ignored_unsolicited_events_handlers (MM_BROADBAND_MODEM_VIA (self)); + + /* Now reset the unsolicited messages we'll handle when enabled */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_VIA (self), FALSE); +} + +/*****************************************************************************/ + +MMBroadbandModemVia * +mm_broadband_modem_via_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_VIA, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_via_init (MMBroadbandModemVia *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_VIA, + MMBroadbandModemViaPrivate); + + /* Prepare regular expressions to setup */ + self->priv->hrssilvl_regex = g_regex_new ("\\r\\n\\^HRSSILVL:(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->mode_regex = g_regex_new ("\\r\\n\\^MODE:(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->dosession_regex = g_regex_new ("\\r\\n\\+DOSESSION:(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->simst_regex = g_regex_new ("\\r\\n\\^SIMST:(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->simst_regex = g_regex_new ("\\r\\n\\+VPON:(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->creg_regex = g_regex_new ("\\r\\n\\+CREG:(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->vrom_regex = g_regex_new ("\\r\\n\\+VROM:(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->vser_regex = g_regex_new ("\\r\\n\\+VSER:(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ciev_regex = g_regex_new ("\\r\\n\\+CIEV:(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->vpup_regex = g_regex_new ("\\r\\n\\+VPUP:(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemVia *self = MM_BROADBAND_MODEM_VIA (object); + + g_regex_unref (self->priv->hrssilvl_regex); + g_regex_unref (self->priv->mode_regex); + g_regex_unref (self->priv->dosession_regex); + g_regex_unref (self->priv->simst_regex); + g_regex_unref (self->priv->simst_regex); + g_regex_unref (self->priv->creg_regex); + g_regex_unref (self->priv->vrom_regex); + g_regex_unref (self->priv->vser_regex); + g_regex_unref (self->priv->ciev_regex); + g_regex_unref (self->priv->vpup_regex); + + G_OBJECT_CLASS (mm_broadband_modem_via_parent_class)->finalize (object); +} + +static void +iface_modem_cdma_init (MMIfaceModemCdma *iface) +{ + iface_modem_cdma_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = modem_cdma_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_cdma_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish; + iface->setup_registration_checks = setup_registration_checks; + iface->setup_registration_checks_finish = setup_registration_checks_finish; + iface->get_detailed_registration_state = get_detailed_registration_state; + iface->get_detailed_registration_state_finish = get_detailed_registration_state_finish; +} + +static void +mm_broadband_modem_via_class_init (MMBroadbandModemViaClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemViaPrivate)); + + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/via/mm-broadband-modem-via.h b/src/plugins/via/mm-broadband-modem-via.h new file mode 100644 index 00000000..2a31117f --- /dev/null +++ b/src/plugins/via/mm-broadband-modem-via.h @@ -0,0 +1,49 @@ +/* -*- 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) 2012 Red Hat, Inc. + */ + +#ifndef MM_BROADBAND_MODEM_VIA_H +#define MM_BROADBAND_MODEM_VIA_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_VIA (mm_broadband_modem_via_get_type ()) +#define MM_BROADBAND_MODEM_VIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_VIA, MMBroadbandModemVia)) +#define MM_BROADBAND_MODEM_VIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_VIA, MMBroadbandModemViaClass)) +#define MM_IS_BROADBAND_MODEM_VIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_VIA)) +#define MM_IS_BROADBAND_MODEM_VIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_VIA)) +#define MM_BROADBAND_MODEM_VIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_VIA, MMBroadbandModemViaClass)) + +typedef struct _MMBroadbandModemVia MMBroadbandModemVia; +typedef struct _MMBroadbandModemViaClass MMBroadbandModemViaClass; +typedef struct _MMBroadbandModemViaPrivate MMBroadbandModemViaPrivate; + +struct _MMBroadbandModemVia { + MMBroadbandModem parent; + MMBroadbandModemViaPrivate *priv; +}; + +struct _MMBroadbandModemViaClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_via_get_type (void); + +MMBroadbandModemVia *mm_broadband_modem_via_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_VIA_H */ diff --git a/src/plugins/via/mm-plugin-via.c b/src/plugins/via/mm-plugin-via.c new file mode 100644 index 00000000..b1939092 --- /dev/null +++ b/src/plugins/via/mm-plugin-via.c @@ -0,0 +1,84 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2012 Red Hat, Inc. + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem-via.h" +#include "mm-plugin-via.h" + +G_DEFINE_TYPE (MMPluginVia, mm_plugin_via, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_via_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + static const mm_str_pair product_strings[] = { { (gchar *) "via", (gchar *) "cbp7" }, + { (gchar *) "fusion", (gchar *) "2770p" }, + { NULL, NULL } }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_VIA, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_PRODUCT_STRINGS, product_strings, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_REQUIRED_QCDM, TRUE, + NULL)); +} + +static void +mm_plugin_via_init (MMPluginVia *self) +{ +} + +static void +mm_plugin_via_class_init (MMPluginViaClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/via/mm-plugin-via.h b/src/plugins/via/mm-plugin-via.h new file mode 100644 index 00000000..68d8c5f6 --- /dev/null +++ b/src/plugins/via/mm-plugin-via.h @@ -0,0 +1,46 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2012 Red Hat, Inc. + */ + +#ifndef MM_PLUGIN_VIA_H +#define MM_PLUGIN_VIA_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_VIA (mm_plugin_via_get_type ()) +#define MM_PLUGIN_VIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_VIA, MMPluginVia)) +#define MM_PLUGIN_VIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_VIA, MMPluginViaClass)) +#define MM_IS_PLUGIN_VIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_VIA)) +#define MM_IS_PLUGIN_VIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_VIA)) +#define MM_PLUGIN_VIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_VIA, MMPluginViaClass)) + +typedef struct { + MMPlugin parent; +} MMPluginVia; + +typedef struct { + MMPluginClass parent; +} MMPluginViaClass; + +GType mm_plugin_via_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_VIA_H */ diff --git a/src/plugins/wavecom/mm-broadband-modem-wavecom.c b/src/plugins/wavecom/mm-broadband-modem-wavecom.c new file mode 100644 index 00000000..521e72de --- /dev/null +++ b/src/plugins/wavecom/mm-broadband-modem-wavecom.c @@ -0,0 +1,1320 @@ +/* -*- 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 Ammonit Measurement GmbH + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-serial-parsers.h" +#include "mm-modem-helpers.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-wavecom.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); + +static MMIfaceModem3gpp *iface_modem_3gpp_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemWavecom, mm_broadband_modem_wavecom, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)) + +#define WAVECOM_MS_CLASS_CC_IDSTR "\"CC\"" +#define WAVECOM_MS_CLASS_CG_IDSTR "\"CG\"" +#define WAVECOM_MS_CLASS_B_IDSTR "\"B\"" +#define WAVECOM_MS_CLASS_A_IDSTR "\"A\"" + +/* Setup relationship between 2G bands in the modem (identified by a + * single digit in ASCII) and the bitmask in ModemManager. */ +typedef struct { + gchar wavecom_band; + guint n_mm_bands; + MMModemBand mm_bands[4]; +} WavecomBand2G; +static const WavecomBand2G bands_2g[] = { + { '0', 1, { MM_MODEM_BAND_G850, 0, 0, 0 }}, + { '1', 1, { MM_MODEM_BAND_EGSM, 0, 0, 0 }}, + { '2', 1, { MM_MODEM_BAND_DCS, 0, 0, 0 }}, + { '3', 1, { MM_MODEM_BAND_PCS, 0, 0, 0 }}, + { '4', 2, { MM_MODEM_BAND_G850, MM_MODEM_BAND_PCS, 0, 0 }}, + { '5', 2, { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, 0, 0 }}, + { '6', 2, { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_PCS, 0, 0 }}, + { '7', 4, { MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS, MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM }} +}; + +/* Setup relationship between the 3G band bitmask in the modem and the bitmask + * in ModemManager. */ +typedef struct { + guint32 wavecom_band_flag; + MMModemBand mm_band; +} WavecomBand3G; +static const WavecomBand3G bands_3g[] = { + { (1 << 0), MM_MODEM_BAND_UTRAN_1 }, + { (1 << 1), MM_MODEM_BAND_UTRAN_2 }, + { (1 << 2), MM_MODEM_BAND_UTRAN_3 }, + { (1 << 3), MM_MODEM_BAND_UTRAN_4 }, + { (1 << 4), MM_MODEM_BAND_UTRAN_5 }, + { (1 << 5), MM_MODEM_BAND_UTRAN_6 }, + { (1 << 6), MM_MODEM_BAND_UTRAN_7 }, + { (1 << 7), MM_MODEM_BAND_UTRAN_8 }, + { (1 << 8), MM_MODEM_BAND_UTRAN_9 } +}; + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +supported_ms_classes_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GArray *all; + GArray *combinations; + GArray *filtered; + const gchar *response; + GError *error = NULL; + MMModemModeCombination mode; + MMModemMode mode_all; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + response = mm_strip_tag (response, "+CGCLASS:"); + mode_all = MM_MODEM_MODE_NONE; + if (strstr (response, WAVECOM_MS_CLASS_A_IDSTR)) + mode_all |= MM_MODEM_MODE_3G; + if (strstr (response, WAVECOM_MS_CLASS_B_IDSTR)) + mode_all |= (MM_MODEM_MODE_2G | MM_MODEM_MODE_CS); + if (strstr (response, WAVECOM_MS_CLASS_CG_IDSTR)) + mode_all |= MM_MODEM_MODE_2G; + if (strstr (response, WAVECOM_MS_CLASS_CC_IDSTR)) + mode_all |= MM_MODEM_MODE_CS; + + /* If none received, error */ + if (mode_all == MM_MODEM_MODE_NONE) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get supported mobile station classes: '%s'", + response); + g_object_unref (task); + return; + } + + /* Build ALL mask */ + all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1); + mode.allowed = mode_all; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (all, mode); + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 7); + /* CS only */ + mode.allowed = MM_MODEM_MODE_CS; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* CS and 2G */ + mode.allowed = (MM_MODEM_MODE_CS | MM_MODEM_MODE_2G); + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G, 2G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + /* 2G and 3G, 3G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CGCLASS=?", + 3, + FALSE, + (GAsyncReadyCallback)supported_ms_classes_query_ready, + task); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +typedef struct { + MMModemMode allowed; + MMModemMode preferred; +} LoadCurrentModesResult; + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + g_autofree LoadCurrentModesResult *result = NULL; + + result = g_task_propagate_pointer (G_TASK (res), error); + if (!result) + return FALSE; + + *allowed = result->allowed; + *preferred = result->preferred; + return TRUE; +} + +static void +wwsm_read_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree LoadCurrentModesResult *result = NULL; + const gchar *response; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + result = g_new0 (LoadCurrentModesResult, 1); + result->allowed = MM_MODEM_MODE_NONE; + result->preferred = MM_MODEM_MODE_NONE; + + /* Possible responses: + * +WWSM: 0 (2G only) + * +WWSM: 1 (3G only) + * +WWSM: 2,0 (Any) + * +WWSM: 2,1 (2G preferred) + * +WWSM: 2,2 (3G preferred) + */ + r = g_regex_new ("\\r\\n\\+WWSM: ([0-2])(,([0-2]))?.*$", 0, 0, NULL); + g_assert (r != NULL); + + if (g_regex_match (r, response, 0, &match_info)) { + guint allowed = 0; + + if (mm_get_uint_from_match_info (match_info, 1, &allowed)) { + switch (allowed) { + case 0: + result->allowed = MM_MODEM_MODE_2G; + result->preferred = MM_MODEM_MODE_NONE; + break; + case 1: + result->allowed = MM_MODEM_MODE_3G; + result->preferred = MM_MODEM_MODE_NONE; + break; + case 2: { + guint preferred = 0; + + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + + /* 3, to avoid the comma */ + if (mm_get_uint_from_match_info (match_info, 3, &preferred)) { + switch (preferred) { + case 0: + result->preferred = MM_MODEM_MODE_NONE; + break; + case 1: + result->preferred = MM_MODEM_MODE_2G; + break; + case 2: + result->preferred = MM_MODEM_MODE_3G; + break; + default: + g_warn_if_reached (); + break; + } + } + break; + } + default: + g_warn_if_reached (); + break; + } + } + } + + if (result->allowed == MM_MODEM_MODE_NONE) + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unknown wireless data service reply: '%s'", + response); + else + g_task_return_pointer (task, g_steal_pointer (&result), g_free); + g_object_unref (task); +} + +static void +current_ms_class_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autofree LoadCurrentModesResult *result = NULL; + const gchar *response; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + response = mm_strip_tag (response, "+CGCLASS:"); + + if (strncmp (response, + WAVECOM_MS_CLASS_A_IDSTR, + strlen (WAVECOM_MS_CLASS_A_IDSTR)) == 0) { + mm_obj_dbg (self, "configured as a Class A mobile station"); + /* For 3G devices, query WWSM status */ + mm_base_modem_at_command (self, + "+WWSM?", + 3, + FALSE, + (GAsyncReadyCallback)wwsm_read_ready, + task); + return; + } + + result = g_new0 (LoadCurrentModesResult, 1); + result->allowed = MM_MODEM_MODE_NONE; + result->preferred = MM_MODEM_MODE_NONE; + + if (strncmp (response, + WAVECOM_MS_CLASS_B_IDSTR, + strlen (WAVECOM_MS_CLASS_B_IDSTR)) == 0) { + mm_obj_dbg (self, "configured as a Class B mobile station"); + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_CS); + result->preferred = MM_MODEM_MODE_2G; + } else if (strncmp (response, + WAVECOM_MS_CLASS_CG_IDSTR, + strlen (WAVECOM_MS_CLASS_CG_IDSTR)) == 0) { + mm_obj_dbg (self, "configured as a Class CG mobile station"); + result->allowed = MM_MODEM_MODE_2G; + result->preferred = MM_MODEM_MODE_NONE; + } else if (strncmp (response, + WAVECOM_MS_CLASS_CC_IDSTR, + strlen (WAVECOM_MS_CLASS_CC_IDSTR)) == 0) { + mm_obj_dbg (self, "configured as a Class CC mobile station"); + result->allowed = MM_MODEM_MODE_CS; + result->preferred = MM_MODEM_MODE_NONE; + } + + if (result->allowed == MM_MODEM_MODE_NONE) + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unknown mobile station class: '%s'", + response); + else + g_task_return_pointer (task, g_steal_pointer (&result), g_free); + g_object_unref (task); +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CGCLASS?", + 3, + FALSE, + (GAsyncReadyCallback)current_ms_class_ready, + task); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +typedef struct { + gchar *cgclass_command; + gchar *wwsm_command; +} SetCurrentModesContext; + +static void +set_current_modes_context_free (SetCurrentModesContext *ctx) +{ + g_free (ctx->cgclass_command); + g_free (ctx->wwsm_command); + g_free (ctx); +} + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +wwsm_update_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 +cgclass_update_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + SetCurrentModesContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!ctx->wwsm_command) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + ctx->wwsm_command, + 3, + FALSE, + (GAsyncReadyCallback)wwsm_update_ready, + task); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + SetCurrentModesContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_new0 (SetCurrentModesContext, 1); + g_task_set_task_data (task, ctx, (GDestroyNotify) set_current_modes_context_free); + + /* Handle ANY/NONE */ + if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE) { + if (mm_iface_modem_is_3g (self)) { + allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + preferred = MM_MODEM_MODE_NONE; + } else { + allowed = (MM_MODEM_MODE_CS | MM_MODEM_MODE_2G); + preferred = MM_MODEM_MODE_2G; + } + } + + if (allowed == MM_MODEM_MODE_CS) + ctx->cgclass_command = g_strdup ("+CGCLASS=" WAVECOM_MS_CLASS_CC_IDSTR); + else if (allowed == MM_MODEM_MODE_2G) + ctx->cgclass_command = g_strdup ("+CGCLASS=" WAVECOM_MS_CLASS_CG_IDSTR); + else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_CS) && + preferred == MM_MODEM_MODE_2G) + ctx->cgclass_command = g_strdup ("+CGCLASS=" WAVECOM_MS_CLASS_B_IDSTR); + else if (allowed & MM_MODEM_MODE_3G) { + if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { + if (preferred == MM_MODEM_MODE_2G) + ctx->wwsm_command = g_strdup ("+WWSM=2,1"); + else if (preferred == MM_MODEM_MODE_3G) + ctx->wwsm_command = g_strdup ("+WWSM=2,2"); + else if (preferred == MM_MODEM_MODE_NONE) + ctx->wwsm_command = g_strdup ("+WWSM=2,0"); + } else if (allowed == MM_MODEM_MODE_3G) + ctx->wwsm_command = g_strdup ("+WWSM=1"); + + if (ctx->wwsm_command) + ctx->cgclass_command = g_strdup ("+CGCLASS=" WAVECOM_MS_CLASS_A_IDSTR); + } + + if (!ctx->cgclass_command) { + g_autofree gchar *allowed_str = NULL; + g_autofree gchar *preferred_str = NULL; + + 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_object_unref (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + ctx->cgclass_command, + 3, + FALSE, + (GAsyncReadyCallback)cgclass_update_ready, + task); +} + +/*****************************************************************************/ +/* Load supported bands (Modem interface) */ + +static GArray * +load_supported_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_supported_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + GArray *bands; + + task = g_task_new (self, NULL, callback, user_data); + + /* We do assume that we already know if the modem is 2G-only, 3G-only or + * 2G+3G. This is checked quite before trying to load supported bands. */ + +#define _g_array_insert_enum(array,index,type,val) do { \ + type aux = (type)val; \ + g_array_insert_val (array, index, aux); \ + } while (0) + + /* Add 3G-specific bands */ + if (mm_iface_modem_is_3g (self)) { + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 10); + _g_array_insert_enum (bands, 0, MMModemBand, MM_MODEM_BAND_UTRAN_1); + _g_array_insert_enum (bands, 1, MMModemBand, MM_MODEM_BAND_UTRAN_2); + _g_array_insert_enum (bands, 2, MMModemBand, MM_MODEM_BAND_UTRAN_3); + _g_array_insert_enum (bands, 3, MMModemBand, MM_MODEM_BAND_UTRAN_4); + _g_array_insert_enum (bands, 4, MMModemBand, MM_MODEM_BAND_UTRAN_5); + _g_array_insert_enum (bands, 5, MMModemBand, MM_MODEM_BAND_UTRAN_6); + _g_array_insert_enum (bands, 6, MMModemBand, MM_MODEM_BAND_UTRAN_7); + _g_array_insert_enum (bands, 7, MMModemBand, MM_MODEM_BAND_UTRAN_8); + _g_array_insert_enum (bands, 8, MMModemBand, MM_MODEM_BAND_UTRAN_9); + } + /* Add 2G-specific bands */ + else { + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4); + _g_array_insert_enum (bands, 0, MMModemBand, MM_MODEM_BAND_EGSM); + _g_array_insert_enum (bands, 1, MMModemBand, MM_MODEM_BAND_DCS); + _g_array_insert_enum (bands, 2, MMModemBand, MM_MODEM_BAND_PCS); + _g_array_insert_enum (bands, 3, MMModemBand, MM_MODEM_BAND_G850); + } + + g_task_return_pointer (task, bands, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Load current bands (Modem interface) */ + +static GArray * +load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +get_2g_band_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + const gchar *p; + GError *error = NULL; + GArray *bands_array = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + p = mm_strip_tag (response, "+WMBS:"); + if (p) { + guint i; + + for (i = 0; i < G_N_ELEMENTS (bands_2g); i++) { + if (bands_2g[i].wavecom_band == *p) { + guint j; + + if (G_UNLIKELY (!bands_array)) + bands_array = g_array_new (FALSE, FALSE, sizeof (MMModemBand)); + + for (j = 0; j < bands_2g[i].n_mm_bands; j++) + g_array_append_val (bands_array, bands_2g[i].mm_bands[j]); + } + } + } + + if (!bands_array) + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse current bands reply: '%s'", + response); + else + g_task_return_pointer (task, bands_array, (GDestroyNotify)g_array_unref); + g_object_unref (task); +} + +static void +get_3g_band_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + const gchar *p; + GError *error = NULL; + GArray *bands_array = NULL; + guint32 wavecom_band; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Example reply: + * AT+WUBS? --> + * <-- +WUBS: "3",1 + * <-- OK + * The "3" meaning here Band I and II are selected. + */ + + p = mm_strip_tag (response, "+WUBS:"); + if (*p == '"') + p++; + + wavecom_band = atoi (p); + if (wavecom_band > 0) { + guint i; + + for (i = 0; i < G_N_ELEMENTS (bands_3g); i++) { + if (bands_3g[i].wavecom_band_flag & wavecom_band) { + if (G_UNLIKELY (!bands_array)) + bands_array = g_array_new (FALSE, FALSE, sizeof (MMModemBand)); + g_array_append_val (bands_array, bands_3g[i].mm_band); + } + } + } + + if (!bands_array) + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse current bands reply: '%s'", + response); + else + g_task_return_pointer (task, bands_array, (GDestroyNotify)g_array_unref); + g_object_unref (task); +} + +static void +load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + if (mm_iface_modem_is_3g (self)) + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT+WUBS?", + 3, + FALSE, + (GAsyncReadyCallback)get_3g_band_ready, + task); + else + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT+WMBS?", + 3, + FALSE, + (GAsyncReadyCallback)get_2g_band_ready, + task); +} + +/*****************************************************************************/ +/* Set current_bands (Modem interface) */ + +static gboolean +set_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +wmbs_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +set_bands_3g (GTask *task, + GArray *bands_array) +{ + MMBroadbandModemWavecom *self; + guint wavecom_band = 0; + guint i; + g_autoptr(GArray) bands_array_final = NULL; + g_autofree gchar *cmd = NULL; + + self = g_task_get_source_object (task); + + /* The special case of ANY should be treated separately. */ + if (bands_array->len == 1 && + g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) { + /* We build an array with all bands to set; so that we use the same + * logic to build the cinterion_band, and so that we can log the list of + * bands being set properly */ + bands_array_final = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), G_N_ELEMENTS (bands_3g)); + for (i = 0; i < G_N_ELEMENTS (bands_3g); i++) + g_array_append_val (bands_array_final, bands_3g[i].mm_band); + } else + bands_array_final = g_array_ref (bands_array); + + for (i = 0; i < G_N_ELEMENTS (bands_3g); i++) { + guint j; + + for (j = 0; j < bands_array_final->len; j++) { + if (g_array_index (bands_array_final, MMModemBand, j) == bands_3g[i].mm_band) { + wavecom_band |= bands_3g[i].wavecom_band_flag; + break; + } + } + } + + if (wavecom_band == 0) { + g_autofree gchar *bands_string = NULL; + + bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands_array_final->data, bands_array_final->len); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "The given band combination is not supported: '%s'", + bands_string); + g_object_unref (task); + return; + } + + cmd = g_strdup_printf ("+WMBS=\"%u\",1", wavecom_band); + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 3, + FALSE, + (GAsyncReadyCallback)wmbs_set_ready, + task); +} + +static void +set_bands_2g (GTask *task, + GArray *bands_array) +{ + MMBroadbandModemWavecom *self; + gchar wavecom_band = '\0'; + guint i; + g_autofree gchar *cmd = NULL; + g_autoptr(GArray) bands_array_final = NULL; + + self = g_task_get_source_object (task); + + /* If the iface properly checked the given list against the supported bands, + * it's not possible to get an array longer than 4 here. */ + g_assert (bands_array->len <= 4); + + /* The special case of ANY should be treated separately. */ + if (bands_array->len == 1 && + g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) { + const WavecomBand2G *all; + + /* All bands is the last element in our 2G bands array */ + all = &bands_2g[G_N_ELEMENTS (bands_2g) - 1]; + + /* We build an array with all bands to set; so that we use the same + * logic to build the cinterion_band, and so that we can log the list of + * bands being set properly */ + bands_array_final = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4); + g_array_append_vals (bands_array_final, all->mm_bands, all->n_mm_bands); + } else + bands_array_final = g_array_ref (bands_array); + + for (i = 0; wavecom_band == '\0' && i < G_N_ELEMENTS (bands_2g); i++) { + g_autoptr(GArray) supported_combination = NULL; + + supported_combination = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), bands_2g[i].n_mm_bands); + g_array_append_vals (supported_combination, bands_2g[i].mm_bands, bands_2g[i].n_mm_bands); + + /* Check if the given array is exactly one of the supported combinations */ + if (mm_common_bands_garray_cmp (bands_array_final, supported_combination)) { + wavecom_band = bands_2g[i].wavecom_band; + break; + } + } + + if (wavecom_band == '\0') { + g_autofree gchar *bands_string = NULL; + + bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands_array_final->data, bands_array_final->len); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "The given band combination is not supported: '%s'", + bands_string); + g_object_unref (task); + return; + } + + cmd = g_strdup_printf ("+WMBS=%c,1", wavecom_band); + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 3, + FALSE, + (GAsyncReadyCallback)wmbs_set_ready, + task); +} + +static void +set_current_bands (MMIfaceModem *self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + /* The bands that we get here are previously validated by the interface, and + * that means that ALL the bands given here were also given in the list of + * supported bands. BUT BUT, that doesn't mean that the exact list of bands + * will end up being valid, as not all combinations are possible. E.g, + * Wavecom modems supporting only 2G have specific combinations allowed. + */ + task = g_task_new (self, NULL, callback, user_data); + + if (mm_iface_modem_is_3g (self)) + set_bands_3g (task, bands_array); + else + set_bands_2g (task, bands_array); +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + MMModemAccessTechnology act; + const gchar *p; + const gchar *response; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + p = mm_strip_tag (response, "+WGPRSIND:"); + if (p) { + switch (*p) { + case '1': + /* GPRS only */ + act = MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + break; + case '2': + /* EGPRS/EDGE supported */ + act = MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + break; + case '3': + /* 3G R99 supported */ + act = MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + break; + case '4': + /* HSDPA supported */ + act = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; + break; + case '5': + /* HSUPA supported */ + act = MM_MODEM_ACCESS_TECHNOLOGY_HSUPA; + break; + default: + break; + } + } + + if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse access technologies result: '%s'", + response); + return FALSE; + } + + /* We are reporting ALL 3GPP access technologies here */ + *access_technologies = act; + *mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK; + return TRUE; +} + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+WGPRS=9,2", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Register in network (3GPP interface) */ + +static gboolean +register_in_network_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_registration_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->register_in_network_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +run_parent_registration (GTask *task) +{ + MMBroadbandModemWavecom *self; + const gchar *operator_id; + + self = g_task_get_source_object (task); + operator_id = g_task_get_task_data (task); + + iface_modem_3gpp_parent->register_in_network ( + MM_IFACE_MODEM_3GPP (self), + operator_id, + g_task_get_cancellable (task), + (GAsyncReadyCallback)parent_registration_ready, + task); +} + +static gboolean +parse_network_registration_mode (const gchar *reply, + guint *mode) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + g_assert (mode != NULL); + + if (!reply) + return FALSE; + + r = g_regex_new ("\\+COPS:\\s*(\\d)", G_REGEX_UNGREEDY, 0, NULL); + g_assert (r != NULL); + + g_regex_match (r, reply, 0, &match_info); + + return (g_match_info_matches (match_info) && mm_get_uint_from_match_info (match_info, 1, mode)); +} + +static void +cops_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + guint mode; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!parse_network_registration_mode (response, &mode)) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse current network registration mode: '%s'", + response); + g_object_unref (task); + return; + } + + /* If the modem is not configured for automatic registration, run parent */ + if (mode != 0) { + run_parent_registration (task); + return; + } + + mm_obj_dbg (self, "device is already in automatic registration mode, not requesting it again"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +register_in_network (MMIfaceModem3gpp *self, + const gchar *operator_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, cancellable, callback, user_data); + + /* Store operator id as task data */ + g_task_set_task_data (task, g_strdup (operator_id), g_free); + + /* If requesting automatic registration, we first need to query what the + * current mode is. We must NOT send +COPS=0 if it already is in 0 mode, + * or the device will get stuck. */ + if (operator_id == NULL || operator_id[0] == '\0') { + /* Check which is the current operator selection status */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+COPS?", + 3, + FALSE, + (GAsyncReadyCallback)cops_ready, + task); + return; + } + + /* Otherwise, run parent's implementation right away */ + run_parent_registration (task); +} + +/*****************************************************************************/ +/* After SIM unlock (Modem interface) */ + +static gboolean +modem_after_sim_unlock_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +after_sim_unlock_wait_cb (GTask *task) +{ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +modem_after_sim_unlock (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + /* A short wait is necessary for SIM to become ready, otherwise reloading + * facility lock states may fail with a +CME ERROR: 515 error. + */ + task = g_task_new (self, NULL, callback, user_data); + g_timeout_add_seconds (5, (GSourceFunc)after_sim_unlock_wait_cb, task); +} + +/*****************************************************************************/ +/* Modem power up (Modem interface) */ + +static gboolean +modem_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_power_up (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_obj_warn (self, "not in full functionality status, power-up command is needed"); + mm_obj_warn (self, "the device maybe rebooted"); + + /* Try to go to full functionality mode without rebooting the system. + * Works well if we previously switched off the power with CFUN=4 + */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=1,0", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Use AT+CFUN=4 for power down. It will stop the RF (IMSI detach), and + * keeps access to the SIM */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=4", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +modem_power_off_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_power_off (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CPOF=1", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ + +static void +setup_ports (MMBroadbandModem *self) +{ + gpointer parser; + MMPortSerialAt *primary; + g_autoptr(GRegex) regex = NULL; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_wavecom_parent_class)->setup_ports (self); + + /* Set 9600 baudrate by default in the AT port */ + mm_obj_dbg (self, "baudrate will be set to 9600 bps..."); + primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + if (!primary) + return; + + /* AT+CPIN? replies will never have an OK appended */ + parser = mm_serial_parser_v1_new (); + regex = g_regex_new ("\\r\\n\\+CPIN: .*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, + 0, NULL); + mm_serial_parser_v1_set_custom_regex (parser, regex, NULL); + + mm_port_serial_at_set_response_parser (MM_PORT_SERIAL_AT (primary), + mm_serial_parser_v1_parse, + parser, + mm_serial_parser_v1_destroy); +} + +/*****************************************************************************/ + +MMBroadbandModemWavecom * +mm_broadband_modem_wavecom_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_WAVECOM, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_wavecom_init (MMBroadbandModemWavecom *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->load_supported_bands = load_supported_bands; + iface->load_supported_bands_finish = load_supported_bands_finish; + iface->load_current_bands = load_current_bands; + iface->load_current_bands_finish = load_current_bands_finish; + iface->set_current_bands = set_current_bands; + iface->set_current_bands_finish = set_current_bands_finish; + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->modem_after_sim_unlock = modem_after_sim_unlock; + iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish; + iface->modem_power_up = modem_power_up; + iface->modem_power_up_finish = modem_power_up_finish; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; + iface->modem_power_off = modem_power_off; + iface->modem_power_off_finish = modem_power_off_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->register_in_network = register_in_network; + iface->register_in_network_finish = register_in_network_finish; +} + +static void +mm_broadband_modem_wavecom_class_init (MMBroadbandModemWavecomClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/wavecom/mm-broadband-modem-wavecom.h b/src/plugins/wavecom/mm-broadband-modem-wavecom.h new file mode 100644 index 00000000..89bdbde8 --- /dev/null +++ b/src/plugins/wavecom/mm-broadband-modem-wavecom.h @@ -0,0 +1,50 @@ +/* -*- 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 Ammonit Measurement GmbH + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + * + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#ifndef MM_BROADBAND_MODEM_WAVECOM_H +#define MM_BROADBAND_MODEM_WAVECOM_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_WAVECOM (mm_broadband_modem_wavecom_get_type ()) +#define MM_BROADBAND_MODEM_WAVECOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_WAVECOM, MMBroadbandModemWavecom)) +#define MM_BROADBAND_MODEM_WAVECOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_WAVECOM, MMBroadbandModemWavecomClass)) +#define MM_IS_BROADBAND_MODEM_WAVECOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_WAVECOM)) +#define MM_IS_BROADBAND_MODEM_WAVECOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_WAVECOM)) +#define MM_BROADBAND_MODEM_WAVECOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_WAVECOM, MMBroadbandModemWavecomClass)) + +typedef struct _MMBroadbandModemWavecom MMBroadbandModemWavecom; +typedef struct _MMBroadbandModemWavecomClass MMBroadbandModemWavecomClass; + +struct _MMBroadbandModemWavecom { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemWavecomClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_wavecom_get_type (void); + +MMBroadbandModemWavecom *mm_broadband_modem_wavecom_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_WAVECOM_H */ diff --git a/src/plugins/wavecom/mm-plugin-wavecom.c b/src/plugins/wavecom/mm-plugin-wavecom.c new file mode 100644 index 00000000..8e3f9d2c --- /dev/null +++ b/src/plugins/wavecom/mm-plugin-wavecom.c @@ -0,0 +1,88 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2011 Ammonit Measurement GmbH + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + * + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-plugin-wavecom.h" +#include "mm-broadband-modem-wavecom.h" + +G_DEFINE_TYPE (MMPluginWavecom, mm_plugin_wavecom, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_wavecom_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + static const guint16 vendor_ids[] = { 0x114f, 0 }; + static const gchar *forbidden_drivers[] = { "qcserial", NULL }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_WAVECOM, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_FORBIDDEN_DRIVERS, forbidden_drivers, + MM_PLUGIN_ALLOWED_AT, TRUE, + NULL)); +} + +static void +mm_plugin_wavecom_init (MMPluginWavecom *self) +{ +} + +static void +mm_plugin_wavecom_class_init (MMPluginWavecomClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/wavecom/mm-plugin-wavecom.h b/src/plugins/wavecom/mm-plugin-wavecom.h new file mode 100644 index 00000000..c1d76309 --- /dev/null +++ b/src/plugins/wavecom/mm-plugin-wavecom.h @@ -0,0 +1,49 @@ +/* -*- 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Copyright (C) 2011 Ammonit Measurement GmbH + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + * + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#ifndef MM_PLUGIN_WAVECOM_H +#define MM_PLUGIN_WAVECOM_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_WAVECOM (mm_plugin_wavecom_get_type ()) +#define MM_PLUGIN_WAVECOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_WAVECOM, MMPluginWavecom)) +#define MM_PLUGIN_WAVECOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_WAVECOM, MMPluginWavecomClass)) +#define MM_IS_PLUGIN_WAVECOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_WAVECOM)) +#define MM_IS_PLUGIN_WAVECOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_WAVECOM)) +#define MM_PLUGIN_WAVECOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_WAVECOM, MMPluginWavecomClass)) + +typedef struct { + MMPlugin parent; +} MMPluginWavecom; + +typedef struct { + MMPluginClass parent; +} MMPluginWavecomClass; + +GType mm_plugin_wavecom_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_WAVECOM_H */ diff --git a/src/plugins/x22x/77-mm-x22x-port-types.rules b/src/plugins/x22x/77-mm-x22x-port-types.rules new file mode 100644 index 00000000..40e7677a --- /dev/null +++ b/src/plugins/x22x/77-mm-x22x-port-types.rules @@ -0,0 +1,68 @@ +# do not edit this file, it will be overwritten on update + +# Alcatel One Touch X220D +# Alcatel One Touch X200 +# +# These values were scraped from the X220D's Windows .inf files. jrdmdm.inf +# lists the actual command and data (ie PPP) ports, while jrdser.inf lists the +# aux ports that may be either AT-capable or not but cannot be used for PPP. + + +ACTION!="add|change|move|bind", GOTO="mm_x22x_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1bbb", GOTO="mm_x22x_generic_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0b3c", GOTO="mm_x22x_olivetti_vendorcheck" +GOTO="mm_x22x_port_types_end" + +# Generic JRD devices --------------------------- + +LABEL="mm_x22x_generic_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Alcatel X200 +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{ID_MM_X22X_TAGGED}="1" + +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{ID_MM_X22X_TAGGED}="1" + +# Archos G9 +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_X22X_PORT_TYPE_NMEA}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_X22X_PORT_TYPE_VOICE}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{ID_MM_X22X_TAGGED}="1" + +# Alcaltel X602D +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{ID_MM_X22X_TAGGED}="1" + +GOTO="mm_x22x_port_types_end" + +# Olivetti devices --------------------------- + +LABEL="mm_x22x_olivetti_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Olicard 200 +ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{ID_MM_X22X_TAGGED}="1" + +GOTO="mm_x22x_port_types_end" + +LABEL="mm_x22x_port_types_end" diff --git a/src/plugins/x22x/mm-broadband-modem-x22x.c b/src/plugins/x22x/mm-broadband-modem-x22x.c new file mode 100644 index 00000000..1ce32f57 --- /dev/null +++ b/src/plugins/x22x/mm-broadband-modem-x22x.c @@ -0,0 +1,428 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-broadband-modem-x22x.h" + +static void iface_modem_init (MMIfaceModem *iface); + +static MMIfaceModem *iface_modem_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemX22x, mm_broadband_modem_x22x, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)) + +struct _MMBroadbandModemX22xPrivate { + GRegex *mode_regex; + GRegex *sysinfo_regex; + GRegex *specc_regex; + GRegex *sperror_regex; +}; + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMModemModeCombination mode; + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + /* Build list of combinations for 3GPP devices */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + const gchar *response; + gchar *str; + gint mode = -1; + GError *match_error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + r = g_regex_new ("\\+SYSSEL:\\s*(\\d+),(\\d+),(\\d+),(\\d+)", G_REGEX_UNGREEDY, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &match_error)) { + if (match_error) { + g_propagate_error (error, match_error); + } else { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match +SYSSEL reply: %s", response); + } + return FALSE; + } + + str = g_match_info_fetch (match_info, 3); + mode = atoi (str); + g_free (str); + + switch (mode) { + case 0: + *allowed = MM_MODEM_MODE_ANY; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + case 1: + *allowed = MM_MODEM_MODE_2G; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + case 2: + *allowed = MM_MODEM_MODE_3G; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + default: + break; + } + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse mode/tech response: Unexpected mode '%d'", mode); + return FALSE; +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+SYSSEL?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +allowed_mode_update_ready (MMBroadbandModemX22x *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (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); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *command; + gint syssel = -1; + + task = g_task_new (self, NULL, callback, user_data); + + if (allowed == MM_MODEM_MODE_2G) + syssel = 1; + else if (allowed == MM_MODEM_MODE_3G) + syssel = 2; + else if (allowed == MM_MODEM_MODE_ANY && + preferred == MM_MODEM_MODE_NONE) + syssel = 0; + else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G) && + preferred == MM_MODEM_MODE_NONE) + syssel = 0; + else { + 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_object_unref (task); + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("+SYSSEL=,%d,0", syssel); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)allowed_mode_update_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + const gchar *result; + + result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!result) + return FALSE; + + result = mm_strip_tag (result, "+SSND:"); + *access_technologies = mm_string_to_access_tech (result); + *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; + return TRUE; +} + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+SSND?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +set_ignored_unsolicited_events_handlers (MMBroadbandModemX22x *self) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable/disable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->mode_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->sysinfo_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->specc_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->sperror_regex, + NULL, NULL, NULL); + } +} + +static void +setup_ports (MMBroadbandModem *self) +{ + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_x22x_parent_class)->setup_ports (self); + + /* Unsolicited messages to always ignore */ + set_ignored_unsolicited_events_handlers (MM_BROADBAND_MODEM_X22X (self)); +} + +/*****************************************************************************/ + +MMBroadbandModemX22x * +mm_broadband_modem_x22x_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_X22X, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_x22x_init (MMBroadbandModemX22x *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_X22X, + MMBroadbandModemX22xPrivate); + + self->priv->mode_regex = g_regex_new ("\\r\\n\\^MODE:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->sysinfo_regex = g_regex_new ("\\r\\n\\^SYSINFO:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->specc_regex = g_regex_new ("\\r\\n\\+SPECC\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->sperror_regex = g_regex_new ("\\r\\n\\+SPERROR:.+\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemX22x *self = MM_BROADBAND_MODEM_X22X (object); + + g_regex_unref (self->priv->mode_regex); + g_regex_unref (self->priv->sysinfo_regex); + g_regex_unref (self->priv->specc_regex); + g_regex_unref (self->priv->sperror_regex); + G_OBJECT_CLASS (mm_broadband_modem_x22x_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; +} + +static void +mm_broadband_modem_x22x_class_init (MMBroadbandModemX22xClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemX22xPrivate)); + + object_class->finalize = finalize; + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/x22x/mm-broadband-modem-x22x.h b/src/plugins/x22x/mm-broadband-modem-x22x.h new file mode 100644 index 00000000..74c2b48a --- /dev/null +++ b/src/plugins/x22x/mm-broadband-modem-x22x.h @@ -0,0 +1,51 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_X22X_H +#define MM_BROADBAND_MODEM_X22X_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_X22X (mm_broadband_modem_x22x_get_type ()) +#define MM_BROADBAND_MODEM_X22X(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_X22X, MMBroadbandModemX22x)) +#define MM_BROADBAND_MODEM_X22X_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_X22X, MMBroadbandModemX22xClass)) +#define MM_IS_BROADBAND_MODEM_X22X(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_X22X)) +#define MM_IS_BROADBAND_MODEM_X22X_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_X22X)) +#define MM_BROADBAND_MODEM_X22X_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_X22X, MMBroadbandModemX22xClass)) + +typedef struct _MMBroadbandModemX22x MMBroadbandModemX22x; +typedef struct _MMBroadbandModemX22xClass MMBroadbandModemX22xClass; +typedef struct _MMBroadbandModemX22xPrivate MMBroadbandModemX22xPrivate; + +struct _MMBroadbandModemX22x { + MMBroadbandModem parent; + MMBroadbandModemX22xPrivate *priv; +}; + +struct _MMBroadbandModemX22xClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_x22x_get_type (void); + +MMBroadbandModemX22x *mm_broadband_modem_x22x_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_X22X_H */ diff --git a/src/plugins/x22x/mm-plugin-x22x.c b/src/plugins/x22x/mm-plugin-x22x.c new file mode 100644 index 00000000..7b49cff8 --- /dev/null +++ b/src/plugins/x22x/mm-plugin-x22x.c @@ -0,0 +1,255 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-plugin-x22x.h" +#include "mm-broadband-modem-x22x.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi.h" +#endif + +G_DEFINE_TYPE (MMPluginX22x, mm_plugin_x22x, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ +/* Custom init */ + +typedef struct { + MMPortSerialAt *port; + guint retries; +} X22xCustomInitContext; + +static void +x22x_custom_init_context_free (X22xCustomInitContext *ctx) +{ + g_object_unref (ctx->port); + g_slice_free (X22xCustomInitContext, ctx); +} + +static gboolean +x22x_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void x22x_custom_init_step (GTask *task); + +static void +gmr_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + MMPortProbe *probe; + const gchar *p; + const gchar *response; + GError *error = NULL; + + probe = g_task_get_source_object (task); + + response = mm_port_serial_at_command_finish (port, res, &error); + if (error) { + g_error_free (error); + /* Just retry... */ + x22x_custom_init_step (task); + return; + } + + /* Note the lack of a ':' on the GMR; the X200 doesn't send one */ + p = mm_strip_tag (response, "AT+GMR"); + if (p && *p != 'L') { + /* X200 modems have a GMR firmware revision that starts with 'L', and + * as far as I can tell X060s devices have a revision starting with 'C'. + * So use that to determine if the device is an X200, which this plugin + * does supports. + */ + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Not supported with the X22X plugin"); + } else { + mm_obj_dbg (probe, "(X22X) device is supported by this plugin"); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +x22x_custom_init_step (GTask *task) +{ + MMPortProbe *probe; + X22xCustomInitContext *ctx; + GCancellable *cancellable; + + probe = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + cancellable = g_task_get_cancellable (task); + + /* If cancelled, end */ + if (g_cancellable_is_cancelled (cancellable)) { + mm_obj_dbg (probe, "(X22X) no need to keep on running custom init"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (ctx->retries == 0) { + /* In this case, we need the AT command result to decide whether we can + * support this modem or not, so really fail if we didn't get it. */ + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get device revision information"); + g_object_unref (task); + return; + } + + ctx->retries--; + mm_port_serial_at_command ( + ctx->port, + "AT+GMR", + 3, + FALSE, /* raw */ + FALSE, /* allow_cached */ + cancellable, + (GAsyncReadyCallback)gmr_ready, + task); +} + +static void +x22x_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMDevice *device; + X22xCustomInitContext *ctx; + GTask *task; + + ctx = g_slice_new (X22xCustomInitContext); + ctx->port = g_object_ref (port); + ctx->retries = 3; + + task = g_task_new (probe, cancellable, callback, user_data); + g_task_set_check_cancellable (task, FALSE); + g_task_set_task_data (task, ctx, (GDestroyNotify)x22x_custom_init_context_free); + + /* TCT/Alcatel in their infinite wisdom assigned the same USB VID/PID to + * the x060s (Longcheer firmware) and the x200 (X22X, this plugin) and thus + * we can't tell them apart via udev rules. Worse, they both report the + * same +GMM and +GMI, so we're left with just +GMR which is a sketchy way + * to tell modems apart. We can't really use X22X-specific commands + * like AT+SSND because we're not sure if they work when the SIM PIN has not + * been entered yet; many modems have a limited command parser before the + * SIM is unlocked. + */ + device = mm_port_probe_peek_device (probe); + if (mm_device_get_vendor (device) != 0x1bbb || + mm_device_get_product (device) != 0x0000) { + /* If not exactly this vendor/product, just skip */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + x22x_custom_init_step (task); +} + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered X22X modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_x22x_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + /* Vendors: TAMobile and Olivetti */ + static const guint16 vendor_ids[] = { 0x1bbb, 0x0b3c, 0 }; + /* Only handle X22X tagged devices here. */ + static const gchar *udev_tags[] = { + "ID_MM_X22X_TAGGED", + NULL + }; + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (x22x_custom_init), + .finish = G_CALLBACK (x22x_custom_init_finish), + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_X22X, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + NULL)); +} + +static void +mm_plugin_x22x_init (MMPluginX22x *self) +{ +} + +static void +mm_plugin_x22x_class_init (MMPluginX22xClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/x22x/mm-plugin-x22x.h b/src/plugins/x22x/mm-plugin-x22x.h new file mode 100644 index 00000000..bea588c6 --- /dev/null +++ b/src/plugins/x22x/mm-plugin-x22x.h @@ -0,0 +1,42 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_X22X_H +#define MM_PLUGIN_X22X_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_X22X (mm_plugin_x22x_get_type ()) +#define MM_PLUGIN_X22X(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_X22X, MMPluginX22x)) +#define MM_PLUGIN_X22X_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_X22X, MMPluginX22xClass)) +#define MM_IS_PLUGIN_X22X(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_X22X)) +#define MM_IS_PLUGIN_X22X_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_X22X)) +#define MM_PLUGIN_X22X_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_X22X, MMPluginX22xClass)) + +typedef struct { + MMPlugin parent; +} MMPluginX22x; + +typedef struct { + MMPluginClass parent; +} MMPluginX22xClass; + +GType mm_plugin_x22x_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_X22X_H */ diff --git a/src/plugins/xmm/mm-broadband-modem-mbim-xmm.c b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.c new file mode 100644 index 00000000..287c67f7 --- /dev/null +++ b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.c @@ -0,0 +1,138 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" +#include "mm-broadband-modem-mbim-xmm.h" +#include "mm-shared-xmm.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void shared_xmm_init (MMSharedXmm *iface); + +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimXmm, mm_broadband_modem_mbim_xmm, MM_TYPE_BROADBAND_MODEM_MBIM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_XMM, shared_xmm_init)) + +/*****************************************************************************/ + +MMBroadbandModemMbimXmm * +mm_broadband_modem_mbim_xmm_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_XMM, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* MBIM bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE, +#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED + MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED, TRUE, +#endif + NULL); +} + +static void +mm_broadband_modem_mbim_xmm_init (MMBroadbandModemMbimXmm *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface->load_supported_modes = mm_shared_xmm_load_supported_modes; + iface->load_supported_modes_finish = mm_shared_xmm_load_supported_modes_finish; + iface->load_current_modes = mm_shared_xmm_load_current_modes; + iface->load_current_modes_finish = mm_shared_xmm_load_current_modes_finish; + iface->set_current_modes = mm_shared_xmm_set_current_modes; + iface->set_current_modes_finish = mm_shared_xmm_set_current_modes_finish; + + iface->load_supported_bands = mm_shared_xmm_load_supported_bands; + iface->load_supported_bands_finish = mm_shared_xmm_load_supported_bands_finish; + iface->load_current_bands = mm_shared_xmm_load_current_bands; + iface->load_current_bands_finish = mm_shared_xmm_load_current_bands_finish; + iface->set_current_bands = mm_shared_xmm_set_current_bands; + iface->set_current_bands_finish = mm_shared_xmm_set_current_bands_finish; + + /* power up/down already managed via MBIM */ + iface->modem_power_off = mm_shared_xmm_power_off; + iface->modem_power_off_finish = mm_shared_xmm_power_off_finish; + iface->reset = mm_shared_xmm_reset; + iface->reset_finish = mm_shared_xmm_reset_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_xmm_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_xmm_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_xmm_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_xmm_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_xmm_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_xmm_disable_location_gathering_finish; + iface->load_supl_server = mm_shared_xmm_location_load_supl_server; + iface->load_supl_server_finish = mm_shared_xmm_location_load_supl_server_finish; + iface->set_supl_server = mm_shared_xmm_location_set_supl_server; + iface->set_supl_server_finish = mm_shared_xmm_location_set_supl_server_finish; +} + +static MMBroadbandModemClass * +peek_parent_broadband_modem_class (MMSharedXmm *self) +{ + return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbim_xmm_parent_class); +} + +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedXmm *self) +{ + return iface_modem_location_parent; +} + +static void +shared_xmm_init (MMSharedXmm *iface) +{ + iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class; + iface->peek_parent_location_interface = peek_parent_location_interface; +} + +static void +mm_broadband_modem_mbim_xmm_class_init (MMBroadbandModemMbimXmmClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = mm_shared_xmm_setup_ports; +} diff --git a/src/plugins/xmm/mm-broadband-modem-mbim-xmm.h b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.h new file mode 100644 index 00000000..88e87cb7 --- /dev/null +++ b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.h @@ -0,0 +1,47 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_MBIM_XMM_H +#define MM_BROADBAND_MODEM_MBIM_XMM_H + +#include "mm-broadband-modem-mbim.h" + +#define MM_TYPE_BROADBAND_MODEM_MBIM_XMM (mm_broadband_modem_mbim_xmm_get_type ()) +#define MM_BROADBAND_MODEM_MBIM_XMM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM, MMBroadbandModemMbimXmm)) +#define MM_BROADBAND_MODEM_MBIM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_XMM, MMBroadbandModemMbimXmmClass)) +#define MM_IS_BROADBAND_MODEM_MBIM_XMM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM)) +#define MM_IS_BROADBAND_MODEM_MBIM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_XMM)) +#define MM_BROADBAND_MODEM_MBIM_XMM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM, MMBroadbandModemMbimXmmClass)) + +typedef struct _MMBroadbandModemMbimXmm MMBroadbandModemMbimXmm; +typedef struct _MMBroadbandModemMbimXmmClass MMBroadbandModemMbimXmmClass; + +struct _MMBroadbandModemMbimXmm { + MMBroadbandModemMbim parent; +}; + +struct _MMBroadbandModemMbimXmmClass{ + MMBroadbandModemMbimClass parent; +}; + +GType mm_broadband_modem_mbim_xmm_get_type (void); + +MMBroadbandModemMbimXmm *mm_broadband_modem_mbim_xmm_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_XMM_H */ diff --git a/src/plugins/xmm/mm-broadband-modem-xmm.c b/src/plugins/xmm/mm-broadband-modem-xmm.c new file mode 100644 index 00000000..7698ec66 --- /dev/null +++ b/src/plugins/xmm/mm-broadband-modem-xmm.c @@ -0,0 +1,151 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" +#include "mm-broadband-modem-xmm.h" +#include "mm-shared-xmm.h" + + +static void iface_modem_init (MMIfaceModem *iface); +static void shared_xmm_init (MMSharedXmm *iface); +static void iface_modem_signal_init (MMIfaceModemSignal *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); + +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemXmm, mm_broadband_modem_xmm, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_XMM, shared_xmm_init)) + +/*****************************************************************************/ + +MMBroadbandModemXmm * +mm_broadband_modem_xmm_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_XMM, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_xmm_init (MMBroadbandModemXmm *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface->load_supported_modes = mm_shared_xmm_load_supported_modes; + iface->load_supported_modes_finish = mm_shared_xmm_load_supported_modes_finish; + iface->load_current_modes = mm_shared_xmm_load_current_modes; + iface->load_current_modes_finish = mm_shared_xmm_load_current_modes_finish; + iface->set_current_modes = mm_shared_xmm_set_current_modes; + iface->set_current_modes_finish = mm_shared_xmm_set_current_modes_finish; + + iface->load_supported_bands = mm_shared_xmm_load_supported_bands; + iface->load_supported_bands_finish = mm_shared_xmm_load_supported_bands_finish; + iface->load_current_bands = mm_shared_xmm_load_current_bands; + iface->load_current_bands_finish = mm_shared_xmm_load_current_bands_finish; + iface->set_current_bands = mm_shared_xmm_set_current_bands; + iface->set_current_bands_finish = mm_shared_xmm_set_current_bands_finish; + + iface->load_power_state = mm_shared_xmm_load_power_state; + iface->load_power_state_finish = mm_shared_xmm_load_power_state_finish; + iface->modem_power_up = mm_shared_xmm_power_up; + iface->modem_power_up_finish = mm_shared_xmm_power_up_finish; + iface->modem_power_down = mm_shared_xmm_power_down; + iface->modem_power_down_finish = mm_shared_xmm_power_down_finish; + iface->modem_power_off = mm_shared_xmm_power_off; + iface->modem_power_off_finish = mm_shared_xmm_power_off_finish; + iface->reset = mm_shared_xmm_reset; + iface->reset_finish = mm_shared_xmm_reset_finish; +} + + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_xmm_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_xmm_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_xmm_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_xmm_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_xmm_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_xmm_disable_location_gathering_finish; + iface->load_supl_server = mm_shared_xmm_location_load_supl_server; + iface->load_supl_server_finish = mm_shared_xmm_location_load_supl_server_finish; + iface->set_supl_server = mm_shared_xmm_location_set_supl_server; + iface->set_supl_server_finish = mm_shared_xmm_location_set_supl_server_finish; +} + +static MMBroadbandModemClass * +peek_parent_broadband_modem_class (MMSharedXmm *self) +{ + return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_xmm_parent_class); +} + +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedXmm *self) +{ + return iface_modem_location_parent; +} + +static void +iface_modem_signal_init (MMIfaceModemSignal *iface) +{ + iface->check_support = mm_shared_xmm_signal_check_support; + iface->check_support_finish = mm_shared_xmm_signal_check_support_finish; + iface->load_values = mm_shared_xmm_signal_load_values; + iface->load_values_finish = mm_shared_xmm_signal_load_values_finish; +} + +static void +shared_xmm_init (MMSharedXmm *iface) +{ + iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class; + iface->peek_parent_location_interface = peek_parent_location_interface; +} + +static void +mm_broadband_modem_xmm_class_init (MMBroadbandModemXmmClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = mm_shared_xmm_setup_ports; +} diff --git a/src/plugins/xmm/mm-broadband-modem-xmm.h b/src/plugins/xmm/mm-broadband-modem-xmm.h new file mode 100644 index 00000000..f63a4bfc --- /dev/null +++ b/src/plugins/xmm/mm-broadband-modem-xmm.h @@ -0,0 +1,47 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_XMM_H +#define MM_BROADBAND_MODEM_XMM_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_XMM (mm_broadband_modem_xmm_get_type ()) +#define MM_BROADBAND_MODEM_XMM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_XMM, MMBroadbandModemXmm)) +#define MM_BROADBAND_MODEM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_XMM, MMBroadbandModemXmmClass)) +#define MM_IS_BROADBAND_MODEM_XMM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_XMM)) +#define MM_IS_BROADBAND_MODEM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_XMM)) +#define MM_BROADBAND_MODEM_XMM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_XMM, MMBroadbandModemXmmClass)) + +typedef struct _MMBroadbandModemXmm MMBroadbandModemXmm; +typedef struct _MMBroadbandModemXmmClass MMBroadbandModemXmmClass; + +struct _MMBroadbandModemXmm { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemXmmClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_xmm_get_type (void); + +MMBroadbandModemXmm *mm_broadband_modem_xmm_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_XMM_H */ diff --git a/src/plugins/xmm/mm-modem-helpers-xmm.c b/src/plugins/xmm/mm-modem-helpers-xmm.c new file mode 100644 index 00000000..70e02a8f --- /dev/null +++ b/src/plugins/xmm/mm-modem-helpers-xmm.c @@ -0,0 +1,1003 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-xmm.h" +#include "mm-signal.h" + +/*****************************************************************************/ +/* XACT common config */ + +typedef struct { + guint num; + MMModemBand band; +} XactBandConfig; + +static const XactBandConfig xact_band_config[] = { + /* GSM bands */ + { .num = 900, .band = MM_MODEM_BAND_EGSM }, + { .num = 1800, .band = MM_MODEM_BAND_DCS }, + { .num = 1900, .band = MM_MODEM_BAND_PCS }, + { .num = 850, .band = MM_MODEM_BAND_G850 }, + { .num = 450, .band = MM_MODEM_BAND_G450 }, + { .num = 480, .band = MM_MODEM_BAND_G480 }, + { .num = 750, .band = MM_MODEM_BAND_G750 }, + { .num = 380, .band = MM_MODEM_BAND_G380 }, + { .num = 410, .band = MM_MODEM_BAND_G410 }, + { .num = 710, .band = MM_MODEM_BAND_G710 }, + { .num = 810, .band = MM_MODEM_BAND_G810 }, + /* UMTS bands */ + { .num = 1, .band = MM_MODEM_BAND_UTRAN_1 }, + { .num = 2, .band = MM_MODEM_BAND_UTRAN_2 }, + { .num = 3, .band = MM_MODEM_BAND_UTRAN_3 }, + { .num = 4, .band = MM_MODEM_BAND_UTRAN_4 }, + { .num = 5, .band = MM_MODEM_BAND_UTRAN_5 }, + { .num = 6, .band = MM_MODEM_BAND_UTRAN_6 }, + { .num = 7, .band = MM_MODEM_BAND_UTRAN_7 }, + { .num = 8, .band = MM_MODEM_BAND_UTRAN_8 }, + { .num = 9, .band = MM_MODEM_BAND_UTRAN_9 }, + { .num = 10, .band = MM_MODEM_BAND_UTRAN_10 }, + { .num = 11, .band = MM_MODEM_BAND_UTRAN_11 }, + { .num = 12, .band = MM_MODEM_BAND_UTRAN_12 }, + { .num = 13, .band = MM_MODEM_BAND_UTRAN_13 }, + { .num = 14, .band = MM_MODEM_BAND_UTRAN_14 }, + { .num = 19, .band = MM_MODEM_BAND_UTRAN_19 }, + { .num = 20, .band = MM_MODEM_BAND_UTRAN_20 }, + { .num = 21, .band = MM_MODEM_BAND_UTRAN_21 }, + { .num = 22, .band = MM_MODEM_BAND_UTRAN_22 }, + { .num = 25, .band = MM_MODEM_BAND_UTRAN_25 }, + /* LTE bands */ + { .num = 101, .band = MM_MODEM_BAND_EUTRAN_1 }, + { .num = 102, .band = MM_MODEM_BAND_EUTRAN_2 }, + { .num = 103, .band = MM_MODEM_BAND_EUTRAN_3 }, + { .num = 104, .band = MM_MODEM_BAND_EUTRAN_4 }, + { .num = 105, .band = MM_MODEM_BAND_EUTRAN_5 }, + { .num = 106, .band = MM_MODEM_BAND_EUTRAN_6 }, + { .num = 107, .band = MM_MODEM_BAND_EUTRAN_7 }, + { .num = 108, .band = MM_MODEM_BAND_EUTRAN_8 }, + { .num = 109, .band = MM_MODEM_BAND_EUTRAN_9 }, + { .num = 110, .band = MM_MODEM_BAND_EUTRAN_10 }, + { .num = 111, .band = MM_MODEM_BAND_EUTRAN_11 }, + { .num = 112, .band = MM_MODEM_BAND_EUTRAN_12 }, + { .num = 113, .band = MM_MODEM_BAND_EUTRAN_13 }, + { .num = 114, .band = MM_MODEM_BAND_EUTRAN_14 }, + { .num = 117, .band = MM_MODEM_BAND_EUTRAN_17 }, + { .num = 118, .band = MM_MODEM_BAND_EUTRAN_18 }, + { .num = 119, .band = MM_MODEM_BAND_EUTRAN_19 }, + { .num = 120, .band = MM_MODEM_BAND_EUTRAN_20 }, + { .num = 121, .band = MM_MODEM_BAND_EUTRAN_21 }, + { .num = 122, .band = MM_MODEM_BAND_EUTRAN_22 }, + { .num = 123, .band = MM_MODEM_BAND_EUTRAN_23 }, + { .num = 124, .band = MM_MODEM_BAND_EUTRAN_24 }, + { .num = 125, .band = MM_MODEM_BAND_EUTRAN_25 }, + { .num = 126, .band = MM_MODEM_BAND_EUTRAN_26 }, + { .num = 127, .band = MM_MODEM_BAND_EUTRAN_27 }, + { .num = 128, .band = MM_MODEM_BAND_EUTRAN_28 }, + { .num = 129, .band = MM_MODEM_BAND_EUTRAN_29 }, + { .num = 130, .band = MM_MODEM_BAND_EUTRAN_30 }, + { .num = 131, .band = MM_MODEM_BAND_EUTRAN_31 }, + { .num = 132, .band = MM_MODEM_BAND_EUTRAN_32 }, + { .num = 133, .band = MM_MODEM_BAND_EUTRAN_33 }, + { .num = 134, .band = MM_MODEM_BAND_EUTRAN_34 }, + { .num = 135, .band = MM_MODEM_BAND_EUTRAN_35 }, + { .num = 136, .band = MM_MODEM_BAND_EUTRAN_36 }, + { .num = 137, .band = MM_MODEM_BAND_EUTRAN_37 }, + { .num = 138, .band = MM_MODEM_BAND_EUTRAN_38 }, + { .num = 139, .band = MM_MODEM_BAND_EUTRAN_39 }, + { .num = 140, .band = MM_MODEM_BAND_EUTRAN_40 }, + { .num = 141, .band = MM_MODEM_BAND_EUTRAN_41 }, + { .num = 142, .band = MM_MODEM_BAND_EUTRAN_42 }, + { .num = 143, .band = MM_MODEM_BAND_EUTRAN_43 }, + { .num = 144, .band = MM_MODEM_BAND_EUTRAN_44 }, + { .num = 145, .band = MM_MODEM_BAND_EUTRAN_45 }, + { .num = 146, .band = MM_MODEM_BAND_EUTRAN_46 }, + { .num = 147, .band = MM_MODEM_BAND_EUTRAN_47 }, + { .num = 148, .band = MM_MODEM_BAND_EUTRAN_48 }, + { .num = 149, .band = MM_MODEM_BAND_EUTRAN_49 }, + { .num = 150, .band = MM_MODEM_BAND_EUTRAN_50 }, + { .num = 151, .band = MM_MODEM_BAND_EUTRAN_51 }, + { .num = 152, .band = MM_MODEM_BAND_EUTRAN_52 }, + { .num = 153, .band = MM_MODEM_BAND_EUTRAN_53 }, + { .num = 154, .band = MM_MODEM_BAND_EUTRAN_54 }, + { .num = 155, .band = MM_MODEM_BAND_EUTRAN_55 }, + { .num = 156, .band = MM_MODEM_BAND_EUTRAN_56 }, + { .num = 157, .band = MM_MODEM_BAND_EUTRAN_57 }, + { .num = 158, .band = MM_MODEM_BAND_EUTRAN_58 }, + { .num = 159, .band = MM_MODEM_BAND_EUTRAN_59 }, + { .num = 160, .band = MM_MODEM_BAND_EUTRAN_60 }, + { .num = 161, .band = MM_MODEM_BAND_EUTRAN_61 }, + { .num = 162, .band = MM_MODEM_BAND_EUTRAN_62 }, + { .num = 163, .band = MM_MODEM_BAND_EUTRAN_63 }, + { .num = 164, .band = MM_MODEM_BAND_EUTRAN_64 }, + { .num = 165, .band = MM_MODEM_BAND_EUTRAN_65 }, + { .num = 166, .band = MM_MODEM_BAND_EUTRAN_66 }, +}; + +#define XACT_NUM_IS_BAND_2G(num) (num > 300) +#define XACT_NUM_IS_BAND_3G(num) (num < 100) +#define XACT_NUM_IS_BAND_4G(num) (num > 100 && num < 300) + +static MMModemBand +xact_num_to_band (guint num) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (xact_band_config); i++) { + if (num == xact_band_config[i].num) + return xact_band_config[i].band; + } + return MM_MODEM_BAND_UNKNOWN; +} + +static guint +xact_band_to_num (MMModemBand band) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (xact_band_config); i++) { + if (band == xact_band_config[i].band) + return xact_band_config[i].num; + } + return 0; +} + +/*****************************************************************************/ +/* XACT=? response parser */ + +/* Index of the array is the XMM-specific value */ +static const MMModemMode xmm_modes[] = { + ( MM_MODEM_MODE_2G ), + ( MM_MODEM_MODE_3G ), + ( MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ), + ( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ), +}; + +gboolean +mm_xmm_parse_xact_test_response (const gchar *response, + gpointer log_object, + GArray **modes_out, + GArray **bands_out, + GError **error) +{ + GError *inner_error = NULL; + GArray *modes = NULL; + GArray *all_modes = NULL; + GArray *filtered = NULL; + GArray *supported = NULL; + GArray *preferred = NULL; + GArray *bands = NULL; + gchar **split = NULL; + guint i; + + MMModemModeCombination all = { + .allowed = MM_MODEM_MODE_NONE, + .preferred = MM_MODEM_MODE_NONE + }; + + g_assert (modes_out && bands_out); + + /* + * AT+XACT=? + * +XACT: (0-6),(0-2),0,1,2,4,5,8,101,102,103,104,105,107,108,111,... + */ + response = mm_strip_tag (response, "+XACT:"); + split = mm_split_string_groups (response); + + if (g_strv_length (split) < 3) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing fields"); + goto out; + } + + /* First group is list of supported modes */ + supported = mm_parse_uint_list (split[0], &inner_error); + if (inner_error) + goto out; + if (!supported) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing modes"); + goto out; + } + + /* Second group is list of possible preferred modes. + * For our purposes, the preferred list may be empty */ + preferred = mm_parse_uint_list (split[1], &inner_error); + if (inner_error) + goto out; + + /* Build array of modes */ + modes = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination)); + + for (i = 0; i < supported->len; i++) { + guint supported_value; + MMModemModeCombination combination; + guint j; + + supported_value = g_array_index (supported, guint, i); + + if (supported_value >= G_N_ELEMENTS (xmm_modes)) { + mm_obj_warn (log_object, "unexpected AcT supported value: %u", supported_value); + continue; + } + + /* Combination without any preferred */ + combination.allowed = xmm_modes[supported_value]; + combination.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (modes, combination); + + if (mm_count_bits_set (combination.allowed) == 1) + continue; + + if (!preferred) + continue; + + for (j = 0; j < preferred->len; j++) { + guint preferred_value; + + preferred_value = g_array_index (preferred, guint, j); + if (preferred_value >= G_N_ELEMENTS (xmm_modes)) { + mm_obj_warn (log_object, "unexpected AcT preferred value: %u", preferred_value); + continue; + } + combination.preferred = xmm_modes[preferred_value]; + if (mm_count_bits_set (combination.preferred) != 1) { + mm_obj_warn (log_object, "AcT preferred value should be a single AcT: %u", preferred_value); + continue; + } + if (!(combination.allowed & combination.preferred)) + continue; + g_array_append_val (modes, combination); + } + } + + if (modes->len == 0) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No modes list built from +XACT=? response"); + goto out; + } + + /* Build array of bands */ + bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand)); + + /* + * The next element at index 2 may be '0'. We will just treat that field as + * any other band field as '0' isn't a supported band, we'll just ignore it. + */ + for (i = 2; split[i]; i++) { + MMModemBand band; + guint num; + + if (!mm_get_uint_from_str (split[i], &num)) { + mm_obj_warn (log_object, "unexpected band value: %s", split[i]); + continue; + } + + if (num == 0) + continue; + + band = xact_num_to_band (num); + if (band == MM_MODEM_BAND_UNKNOWN) { + mm_obj_warn (log_object, "unsupported band value: %s", split[i]); + continue; + } + + g_array_append_val (bands, band); + + if (XACT_NUM_IS_BAND_2G (num)) + all.allowed |= MM_MODEM_MODE_2G; + if (XACT_NUM_IS_BAND_3G (num)) + all.allowed |= MM_MODEM_MODE_3G; + if (XACT_NUM_IS_BAND_4G (num)) + all.allowed |= MM_MODEM_MODE_4G; + } + + if (bands->len == 0) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No bands list built from +XACT=? response"); + goto out; + } + + /* AT+XACT lies about the supported modes, e.g. it may report 2G supported + * for 3G+4G only devices. So, filter out unsupported modes based on the + * supported bands */ + all_modes = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1); + g_array_append_val (all_modes, all); + + filtered = mm_filter_supported_modes (all_modes, modes, log_object); + if (!filtered || filtered->len == 0) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Empty supported mode list after frequency band filtering"); + goto out; + } + + /* success */ + +out: + if (modes) + g_array_unref (modes); + if (all_modes) + g_array_unref (all_modes); + if (supported) + g_array_unref (supported); + if (preferred) + g_array_unref (preferred); + g_strfreev (split); + + if (inner_error) { + if (filtered) + g_array_unref (filtered); + if (bands) + g_array_unref (bands); + g_propagate_error (error, inner_error); + return FALSE; + } + + g_assert (filtered); + *modes_out = filtered; + g_assert (bands); + *bands_out = bands; + return TRUE; +} + +/*****************************************************************************/ +/* AT+XACT? response parser */ + +gboolean +mm_xmm_parse_xact_query_response (const gchar *response, + MMModemModeCombination *mode_out, + GArray **bands_out, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + GArray *bands = NULL; + guint i; + + MMModemModeCombination mode = { + .allowed = MM_MODEM_MODE_NONE, + .preferred = MM_MODEM_MODE_NONE, + }; + + /* At least one */ + g_assert (mode_out || bands_out); + + /* + * AT+XACT? + * +XACT: 4,1,2,1,2,4,5,8,101,102,103,104,105,107,108,111,... + * + * Note: the first 3 fields corresponde to allowed and preferred modes. Only the + * first one of those 3 first fields is mandatory, the other two may be empty. + */ + r = g_regex_new ("\\+XACT: (\\d+),([^,]*),([^,]*),(.*)(?:\\r\\n)?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + if (mode_out) { + guint xmm_mode; + + /* Number at index 1 */ + mm_get_uint_from_match_info (match_info, 1, &xmm_mode); + if (xmm_mode >= G_N_ELEMENTS (xmm_modes)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unsupported XACT AcT value: %u", xmm_mode); + goto out; + } + mode.allowed = xmm_modes[xmm_mode]; + + /* Number at index 2 */ + if (mm_count_bits_set (mode.allowed) > 1 && mm_get_uint_from_match_info (match_info, 2, &xmm_mode)) { + if (xmm_mode >= G_N_ELEMENTS (xmm_modes)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unsupported XACT preferred AcT value: %u", xmm_mode); + goto out; + } + mode.preferred = xmm_modes[xmm_mode]; + } + + /* Number at index 3: ignored */ + } + + if (bands_out) { + gchar *bandstr; + GArray *nums; + + /* Bands start at index 4 */ + bandstr = mm_get_string_unquoted_from_match_info (match_info, 4); + nums = mm_parse_uint_list (bandstr, &inner_error); + g_free (bandstr); + + if (inner_error) + goto out; + if (!nums) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid XACT? response"); + goto out; + } + + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), nums->len); + for (i = 0; i < nums->len; i++) { + MMModemBand band; + + band = xact_num_to_band (g_array_index (nums, guint, i)); + if (band != MM_MODEM_BAND_UNKNOWN) + g_array_append_val (bands, band); + } + g_array_unref (nums); + + if (bands->len == 0) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing current band list"); + goto out; + } + } + } + + /* success */ + +out: + if (inner_error) { + if (bands) + g_array_unref (bands); + g_propagate_error (error, inner_error); + return FALSE; + } + + if (mode_out) { + g_assert (mode.allowed != MM_MODEM_MODE_NONE); + mode_out->allowed = mode.allowed; + mode_out->preferred = mode.preferred; + } + + if (bands_out) { + g_assert (bands); + *bands_out = bands; + } + + return TRUE; +} + +/*****************************************************************************/ +/* AT+XACT=X command builder */ + +static gboolean +append_rat_value (GString *str, + MMModemMode mode, + GError **error) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (xmm_modes); i++) { + if (xmm_modes[i] == mode) { + g_string_append_printf (str, "%u", i); + return TRUE; + } + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No AcT value matches requested mode"); + return FALSE; +} + +gchar * +mm_xmm_build_xact_set_command (const MMModemModeCombination *mode, + const GArray *bands, + GError **error) +{ + GString *command; + + /* At least one required */ + g_assert (mode || bands); + + /* Build command */ + command = g_string_new ("+XACT="); + + /* Mode is optional. If not given, we set all fields as empty */ + if (mode) { + /* Allowed mask */ + if (!append_rat_value (command, mode->allowed, error)) { + g_string_free (command, TRUE); + return NULL; + } + + /* Preferred */ + if (mode->preferred != MM_MODEM_MODE_NONE) { + g_string_append (command, ","); + if (!append_rat_value (command, mode->preferred, error)) { + g_string_free (command, TRUE); + return NULL; + } + /* We never set <PreferredAct2> because that is anyway not part of + * ModemManager's API. In modems with triple GSM/UMTS/LTE mode, the + * <PreferredAct2> is always the highest of the remaining ones. E.g. + * if "2G+3G+4G allowed with 2G preferred", the second preferred one + * would be 4G, not 3G. */ + g_string_append (command, ","); + } else + g_string_append (command, ",,"); + } else + g_string_append (command, ",,"); + + if (bands) { + g_string_append (command, ","); + /* Automatic band selection */ + if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) + g_string_append (command, "0"); + else { + guint i; + + for (i = 0; i < bands->len; i++) { + MMModemBand band; + guint num; + + band = g_array_index (bands, MMModemBand, i); + num = xact_band_to_num (band); + if (!num) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Band unsupported by this plugin: %s", mm_modem_band_get_string (band)); + g_string_free (command, TRUE); + return NULL; + } + + g_string_append_printf (command, "%s%u", i == 0 ? "" : ",", num); + } + } + } + + return g_string_free (command, FALSE); +} + +/*****************************************************************************/ +/* Get mode to apply when ANY */ + +MMModemMode +mm_xmm_get_modem_mode_any (const GArray *combinations) +{ + guint i; + MMModemMode any = MM_MODEM_MODE_NONE; + guint any_bits_set = 0; + + for (i = 0; i < combinations->len; i++) { + MMModemModeCombination *combination; + guint bits_set; + + combination = &g_array_index (combinations, MMModemModeCombination, i); + if (combination->preferred != MM_MODEM_MODE_NONE) + continue; + bits_set = mm_count_bits_set (combination->allowed); + if (bits_set > any_bits_set) { + any_bits_set = bits_set; + any = combination->allowed; + } + } + + /* If combinations were processed via mm_xmm_parse_uact_test_response(), + * we're sure that there will be at least one combination with preferred + * 'none', so there must be some valid combination as result */ + g_assert (any != MM_MODEM_MODE_NONE); + return any; +} + +/*****************************************************************************/ +/* +XCESQ? response parser */ + +gboolean +mm_xmm_parse_xcesq_query_response (const gchar *response, + guint *out_rxlev, + guint *out_ber, + guint *out_rscp, + guint *out_ecn0, + guint *out_rsrq, + guint *out_rsrp, + gint *out_rssnr, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + guint rxlev = 99; + guint ber = 99; + guint rscp = 255; + guint ecn0 = 255; + guint rsrq = 255; + guint rsrp = 255; + gint rssnr = 255; + gboolean success = FALSE; + + g_assert (out_rxlev); + g_assert (out_ber); + g_assert (out_rscp); + g_assert (out_ecn0); + g_assert (out_rsrq); + g_assert (out_rsrp); + g_assert (out_rssnr); + + /* Response may be e.g.: + * +XCESQ: 0,99,99,255,255,24,51,18 + * +XCESQ: 0,99,99,46,31,255,255,255 + * +XCESQ: 0,99,99,255,255,17,45,-2 + */ + r = g_regex_new ("\\+XCESQ: (\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(-?\\d+)(?:\\r\\n)?", 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + /* Ignore "n" value */ + if (!mm_get_uint_from_match_info (match_info, 2, &rxlev)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RXLEV"); + goto out; + } + if (!mm_get_uint_from_match_info (match_info, 3, &ber)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read BER"); + goto out; + } + if (!mm_get_uint_from_match_info (match_info, 4, &rscp)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSCP"); + goto out; + } + if (!mm_get_uint_from_match_info (match_info, 5, &ecn0)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read Ec/N0"); + goto out; + } + if (!mm_get_uint_from_match_info (match_info, 6, &rsrq)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRQ"); + goto out; + } + if (!mm_get_uint_from_match_info (match_info, 7, &rsrp)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRP"); + goto out; + } + if (!mm_get_int_from_match_info (match_info, 8, &rssnr)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSSNR"); + goto out; + } + success = TRUE; + } + +out: + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (!success) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse +XCESQ response: %s", response); + return FALSE; + } + + *out_rxlev = rxlev; + *out_ber = ber; + *out_rscp = rscp; + *out_ecn0 = ecn0; + *out_rsrq = rsrq; + *out_rsrp = rsrp; + *out_rssnr = rssnr; + return TRUE; +} + +static gboolean +rssnr_level_to_rssnr (gint rssnr_level, + gpointer log_object, + gdouble *out_rssnr) +{ + if (rssnr_level <= 100 && + rssnr_level >= -100) { + *out_rssnr = rssnr_level / 2.0; + return TRUE; + } + + if (rssnr_level != 255) + mm_obj_warn (log_object, "unexpected RSSNR level: %u", rssnr_level); + return FALSE; +} + +/*****************************************************************************/ +/* Get extended signal information */ + +gboolean +mm_xmm_xcesq_response_to_signal_info (const gchar *response, + gpointer log_object, + MMSignal **out_gsm, + MMSignal **out_umts, + MMSignal **out_lte, + GError **error) +{ + guint rxlev = 0; + guint ber = 0; + guint rscp_level = 0; + guint ecn0_level = 0; + guint rsrq_level = 0; + guint rsrp_level = 0; + gint rssnr_level = 0; + gdouble rssi = MM_SIGNAL_UNKNOWN; + gdouble rscp = MM_SIGNAL_UNKNOWN; + gdouble ecio = MM_SIGNAL_UNKNOWN; + gdouble rsrq = MM_SIGNAL_UNKNOWN; + gdouble rsrp = MM_SIGNAL_UNKNOWN; + gdouble rssnr = MM_SIGNAL_UNKNOWN; + MMSignal *gsm = NULL; + MMSignal *umts = NULL; + MMSignal *lte = NULL; + + if (!mm_xmm_parse_xcesq_query_response (response, + &rxlev, &ber, + &rscp_level, &ecn0_level, + &rsrq_level, &rsrp_level, + &rssnr_level, error)) + return FALSE; + + /* GERAN RSSI */ + if (mm_3gpp_rxlev_to_rssi (rxlev, log_object, &rssi)) { + gsm = mm_signal_new (); + mm_signal_set_rssi (gsm, rssi); + } + + /* ignore BER */ + + /* UMTS RSCP */ + if (mm_3gpp_rscp_level_to_rscp (rscp_level, log_object, &rscp)) { + umts = mm_signal_new (); + mm_signal_set_rscp (umts, rscp); + } + + /* UMTS EcIo (assumed EcN0) */ + if (mm_3gpp_ecn0_level_to_ecio (ecn0_level, log_object, &ecio)) { + if (!umts) + umts = mm_signal_new (); + mm_signal_set_ecio (umts, ecio); + } + + /* Calculate RSSI if we have ecio and rscp */ + if (umts && ecio != -G_MAXDOUBLE && rscp != -G_MAXDOUBLE) { + mm_signal_set_rssi (umts, rscp - ecio); + } + + /* LTE RSRQ */ + if (mm_3gpp_rsrq_level_to_rsrq (rsrq_level, log_object, &rsrq)) { + lte = mm_signal_new (); + mm_signal_set_rsrq (lte, rsrq); + } + + /* LTE RSRP */ + if (mm_3gpp_rsrp_level_to_rsrp (rsrp_level, log_object, &rsrp)) { + if (!lte) + lte = mm_signal_new (); + mm_signal_set_rsrp (lte, rsrp); + } + + /* LTE RSSNR */ + if (rssnr_level_to_rssnr (rssnr_level, log_object, &rssnr)) { + if (!lte) + lte = mm_signal_new (); + mm_signal_set_snr (lte, rssnr); + } + + if (!gsm && !umts && !lte) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't build detailed signal info"); + return FALSE; + } + + if (out_gsm) + *out_gsm = gsm; + if (out_umts) + *out_umts = umts; + if (out_lte) + *out_lte = lte; + + return TRUE; +} + +/*****************************************************************************/ +/* AT+XLCSLSR=? response parser */ + +static gboolean +number_group_contains_value (const gchar *group, + const gchar *group_name, + guint value, + GError **error) +{ + GArray *aux; + guint i; + gboolean found; + + aux = mm_parse_uint_list (group, NULL); + if (!aux) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Unsupported +XLCSLSR format: invalid %s field format", group_name); + return FALSE; + } + + found = FALSE; + for (i = 0; i < aux->len; i++) { + guint value_i; + + value_i = g_array_index (aux, guint, i); + if (value == value_i) { + found = TRUE; + break; + } + } + + g_array_unref (aux); + return found; +} + +gboolean +mm_xmm_parse_xlcslsr_test_response (const gchar *response, + gboolean *transport_protocol_invalid_supported, + gboolean *transport_protocol_supl_supported, + gboolean *standalone_position_mode_supported, + gboolean *ms_assisted_based_position_mode_supported, + gboolean *loc_response_type_nmea_supported, + gboolean *gnss_type_gps_glonass_supported, + GError **error) +{ + gboolean ret = FALSE; + gchar **groups = NULL; + GError *inner_error = NULL; + + /* + * AT+XLCSLSR=? + * +XLCSLSR:(0-2),(0-3), ,(0,1), ,(0,1),(0 -7200),(0-255),(0-1),(0-2),(1-256),(0,1) + * transport_protocol: 2 (invalid) or 1 (supl) + * pos_mode: 3 (standalone) or 2 (ms assisted/based) + * client_id: <empty> + * client_id_type: <empty> + * mlc_number: <empty> + * mlc_number_type: <empty> + * interval: 1 (seconds) + * service_type_id: <empty> + * pseudonym_indicator: <empty> + * loc_response_type: 1 (NMEA strings) + * nmea_mask: 118 (01110110: GGA,GSA,GSV,RMC,VTG) + * gnss_type: 0 (GPS or GLONASS) + */ + response = mm_strip_tag (response, "+XLCSLSR:"); + groups = mm_split_string_groups (response); + + /* We expect 12 groups */ + if (g_strv_length (groups) < 12) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Unsupported +XLCSLSR format: expected 12 fields"); + goto out; + } + + if (transport_protocol_invalid_supported) { + *transport_protocol_invalid_supported = number_group_contains_value (groups[0], + "transport protocol", + 2, /* invalid */ + &inner_error); + if (inner_error) + goto out; + } + + if (transport_protocol_supl_supported) { + *transport_protocol_supl_supported = number_group_contains_value (groups[0], + "transport protocol", + 1, /* supl */ + &inner_error); + if (inner_error) + goto out; + } + + if (standalone_position_mode_supported) { + *standalone_position_mode_supported = number_group_contains_value (groups[1], + "position mode", + 3, /* standalone */ + &inner_error); + if (inner_error) + goto out; + } + + if (ms_assisted_based_position_mode_supported) { + *ms_assisted_based_position_mode_supported = number_group_contains_value (groups[1], + "position mode", + 2, /* ms assisted/based */ + &inner_error); + if (inner_error) + goto out; + } + + if (loc_response_type_nmea_supported) { + *loc_response_type_nmea_supported = number_group_contains_value (groups[9], + "location response type", + 1, /* NMEA */ + &inner_error); + if (inner_error) + goto out; + } + + if (gnss_type_gps_glonass_supported) { + *gnss_type_gps_glonass_supported = number_group_contains_value (groups[11], + "gnss type", + 0, /* GPS/GLONASS */ + &inner_error); + if (inner_error) + goto out; + } + + ret = TRUE; + + out: + g_strfreev (groups); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + return ret; +} + +/*****************************************************************************/ +/* AT+XLCSSLP? response parser */ + +gboolean +mm_xmm_parse_xlcsslp_query_response (const gchar *response, + gchar **supl_address, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + gchar *address = NULL; + guint port = 0; + + /* + * E.g.: + * +XLCSSLP:1,"www.spirent-lcs.com",7275 + */ + + r = g_regex_new ("\\+XLCSSLP:\\s*(\\d+),([^,]*),(\\d+)(?:\\r\\n)?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (!inner_error && g_match_info_matches (match_info)) { + guint type; + + /* We only support types 0 (IPv4) and 1 (FQDN) */ + mm_get_uint_from_match_info (match_info, 1, &type); + if (type != 0 && type != 1) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Unsupported SUPL server address type (%u) in response: %s", type, response); + goto out; + } + + address = mm_get_string_unquoted_from_match_info (match_info, 2); + mm_get_uint_from_match_info (match_info, 3, &port); + if (!port) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Invalid SUPL address port number in response: %s", response); + goto out; + } + } + +out: + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (supl_address) + *supl_address = g_strdup_printf ("%s:%u", address, port); + g_free (address); + + return TRUE; +} diff --git a/src/plugins/xmm/mm-modem-helpers-xmm.h b/src/plugins/xmm/mm-modem-helpers-xmm.h new file mode 100644 index 00000000..a18f0667 --- /dev/null +++ b/src/plugins/xmm/mm-modem-helpers-xmm.h @@ -0,0 +1,75 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_MODEM_HELPERS_XMM_H +#define MM_MODEM_HELPERS_XMM_H + +#include <glib.h> +#include <ModemManager.h> + +/* AT+XACT=? response parser */ +gboolean mm_xmm_parse_xact_test_response (const gchar *response, + gpointer logger, + GArray **modes_out, + GArray **bands_out, + GError **error); + +/* AT+XACT? response parser */ +gboolean mm_xmm_parse_xact_query_response (const gchar *response, + MMModemModeCombination *mode_out, + GArray **bands_out, + GError **error); + +/* AT+XACT=X command builder */ +gchar *mm_xmm_build_xact_set_command (const MMModemModeCombination *mode, + const GArray *bands, + GError **error); + +/* Mode to apply when ANY */ +MMModemMode mm_xmm_get_modem_mode_any (const GArray *combinations); + +gboolean mm_xmm_parse_xcesq_query_response (const gchar *response, + guint *out_rxlev, + guint *out_ber, + guint *out_rscp, + guint *out_ecn0, + guint *out_rsrq, + guint *out_rsrp, + gint *out_rssnr, + GError **error); + +gboolean mm_xmm_xcesq_response_to_signal_info (const gchar *response, + gpointer log_object, + MMSignal **out_gsm, + MMSignal **out_umts, + MMSignal **out_lte, + GError **error); + +/* AT+XLCSLSR=? response parser */ +gboolean mm_xmm_parse_xlcslsr_test_response (const gchar *response, + gboolean *transport_protocol_invalid_supported, + gboolean *transport_protocol_supl_supported, + gboolean *standalone_position_mode_supported, + gboolean *ms_assisted_based_position_mode_supported, + gboolean *loc_response_type_nmea_supported, + gboolean *gnss_type_gps_glonass_supported, + GError **error); + +/* AT+XLCSSLP? response parser */ +gboolean mm_xmm_parse_xlcsslp_query_response (const gchar *response, + gchar **supl_address, + GError **error); + +#endif /* MM_MODEM_HELPERS_XMM_H */ diff --git a/src/plugins/xmm/mm-shared-xmm.c b/src/plugins/xmm/mm-shared-xmm.c new file mode 100644 index 00000000..90f8867a --- /dev/null +++ b/src/plugins/xmm/mm-shared-xmm.c @@ -0,0 +1,1710 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> +#include <arpa/inet.h> + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-signal.h" +#include "mm-iface-modem-location.h" +#include "mm-base-modem.h" +#include "mm-base-modem-at.h" +#include "mm-shared-xmm.h" +#include "mm-modem-helpers-xmm.h" + +/*****************************************************************************/ +/* Private data context */ + +#define PRIVATE_TAG "shared-xmm-private-tag" +static GQuark private_quark; + +typedef enum { + GPS_ENGINE_STATE_OFF, + GPS_ENGINE_STATE_STANDALONE, + GPS_ENGINE_STATE_AGPS_MSA, + GPS_ENGINE_STATE_AGPS_MSB, +} GpsEngineState; + +typedef struct { + /* Broadband modem class support */ + MMBroadbandModemClass *broadband_modem_class_parent; + + /* Modem interface support */ + GArray *supported_modes; + GArray *supported_bands; + MMModemMode allowed_modes; + + /* Location interface support */ + MMIfaceModemLocation *iface_modem_location_parent; + MMModemLocationSource supported_sources; + MMModemLocationSource enabled_sources; + GpsEngineState gps_engine_state; + MMPortSerialAt *gps_port; + GRegex *xlsrstop_regex; + GRegex *nmea_regex; + + /* Asynchronous GPS engine stop task completion */ + GTask *pending_gps_engine_stop_task; +} Private; + +static void +private_free (Private *priv) +{ + g_assert (!priv->pending_gps_engine_stop_task); + g_clear_object (&priv->gps_port); + if (priv->supported_modes) + g_array_unref (priv->supported_modes); + if (priv->supported_bands) + g_array_unref (priv->supported_bands); + g_regex_unref (priv->xlsrstop_regex); + g_regex_unref (priv->nmea_regex); + g_slice_free (Private, priv); +} + +static Private * +get_private (MMSharedXmm *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); + priv->gps_engine_state = GPS_ENGINE_STATE_OFF; + + /* Setup regex for URCs */ + priv->xlsrstop_regex = g_regex_new ("\\r\\n\\+XLSRSTOP:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + priv->nmea_regex = g_regex_new ("(?:\\r\\n)?(?:\\r\\n)?(\\$G.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + /* Setup parent class' MMBroadbandModemClass */ + g_assert (MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_broadband_modem_class); + priv->broadband_modem_class_parent = MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_broadband_modem_class (self); + + /* Setup parent class' MMIfaceModemLocation */ + g_assert (MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_location_interface); + priv->iface_modem_location_parent = MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_location_interface (self); + + g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); + } + + return priv; +} + +/*****************************************************************************/ +/* Supported modes/bands (Modem interface) */ + +GArray * +mm_shared_xmm_load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + Private *priv; + + if (!g_task_propagate_boolean (G_TASK (res), error)) + return NULL; + + priv = get_private (MM_SHARED_XMM (self)); + g_assert (priv->supported_modes); + return g_array_ref (priv->supported_modes); +} + +GArray * +mm_shared_xmm_load_supported_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + Private *priv; + + if (!g_task_propagate_boolean (G_TASK (res), error)) + return NULL; + + priv = get_private (MM_SHARED_XMM (self)); + g_assert (priv->supported_bands); + return g_array_ref (priv->supported_bands); +} + +static void +xact_test_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_XMM (self)); + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response || + !mm_xmm_parse_xact_test_response (response, + self, + &priv->supported_modes, + &priv->supported_bands, + &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +common_load_supported_modes_bands (GTask *task) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (g_task_get_source_object (task)), + "+XACT=?", + 3, + TRUE, /* allow caching */ + (GAsyncReadyCallback)xact_test_ready, + task); +} + +void +mm_shared_xmm_load_supported_modes (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_XMM (self)); + + if (!priv->supported_modes) { + common_load_supported_modes_bands (task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_xmm_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_XMM (self)); + + if (!priv->supported_bands) { + common_load_supported_modes_bands (task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Current modes (Modem interface) */ + +gboolean +mm_shared_xmm_load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + MMModemModeCombination *result; + + result = g_task_propagate_pointer (G_TASK (res), error); + if (!result) + return FALSE; + + *allowed = result->allowed; + *preferred = result->preferred; + g_free (result); + return TRUE; +} + +static void +xact_query_modes_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + Private *priv; + MMModemModeCombination *result; + + priv = get_private (MM_SHARED_XMM (self)); + result = g_new0 (MMModemModeCombination, 1); + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response || !mm_xmm_parse_xact_query_response (response, result, NULL, &error)) { + priv->allowed_modes = MM_MODEM_MODE_NONE; + g_free (result); + g_task_return_error (task, error); + } else { + priv->allowed_modes = result->allowed; + g_task_return_pointer (task, result, g_free); + } + g_object_unref (task); +} + +void +mm_shared_xmm_load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+XACT?", + 3, + FALSE, + (GAsyncReadyCallback)xact_query_modes_ready, + task); +} + +/*****************************************************************************/ +/* Current bands (Modem interface) */ + +GArray * +mm_shared_xmm_load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return (GArray *) g_task_propagate_pointer (G_TASK (res), error); +} + + +static void +xact_query_bands_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + GArray *result = NULL; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response || + !mm_xmm_parse_xact_query_response (response, NULL, &result, &error)) + g_task_return_error (task, error); + else + g_task_return_pointer (task, result, (GDestroyNotify)g_array_unref); + g_object_unref (task); +} + +void +mm_shared_xmm_load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+XACT?", + 3, + FALSE, + (GAsyncReadyCallback)xact_query_bands_ready, + task); +} + +/*****************************************************************************/ +/* Set current modes (Modem interface) */ + +gboolean +mm_shared_xmm_set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +xact_set_modes_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_xmm_set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMModemModeCombination mode; + gchar *command; + GError *error = NULL; + + task = g_task_new (self, NULL, callback, user_data); + + if (allowed != MM_MODEM_MODE_ANY) { + mode.allowed = allowed; + mode.preferred = preferred; + } else { + Private *priv; + + priv = get_private (MM_SHARED_XMM (self)); + mode.allowed = mm_xmm_get_modem_mode_any (priv->supported_modes); + mode.preferred = MM_MODEM_MODE_NONE; + } + + command = mm_xmm_build_xact_set_command (&mode, NULL, &error); + if (!command) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 10, + FALSE, + (GAsyncReadyCallback)xact_set_modes_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Set current bands (Modem interface) */ + +gboolean +mm_shared_xmm_set_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +xact_set_bands_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static gchar * +validate_and_build_command_set_current_bands (MMSharedXmm *self, + const GArray *bands_array, + const GArray *supported_modes, + MMModemMode allowed_modes, + GError **error) +{ + gboolean band_2g_found = FALSE; + gboolean band_3g_found = FALSE; + gboolean band_4g_found = FALSE; + GArray *unapplied_bands; + GError *inner_error = NULL; + guint i; + + /* ANY applies only to the currently selected modes */ + if (bands_array->len == 1 && g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) { + MMModemModeCombination mode; + MMModemMode unapplied; + + /* If we are enabling automatic band selection to a mode combination that does not include + * all supported modes, warn about it because automatic band selection wouldn't be executed + * for the non-selected modes. + * + * This is a known limitation of the modem firmware. + */ + unapplied = mm_xmm_get_modem_mode_any (supported_modes) & ~(allowed_modes); + if (unapplied != MM_MODEM_MODE_NONE) { + g_autofree gchar *str = NULL; + + str = mm_modem_mode_build_string_from_mask (unapplied); + mm_obj_warn (self, "automatic band selection not applied to non-current modes %s", str); + } + + /* Nothing else to validate, go build the command right away */ + + /* We must create the set command with an explicit set of allowed modes. + * We pass NONE as preferred, but that WON'T change the currently selected preferred mode, + * it will be ignored when the command is processed as an empty field will be given */ + mode.allowed = allowed_modes; + mode.preferred = MM_MODEM_MODE_NONE; + return mm_xmm_build_xact_set_command (&mode, bands_array, error); + } + + unapplied_bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand)); + for (i = 0; i < bands_array->len; i++) { + MMModemBand band; + + band = g_array_index (bands_array, MMModemBand, i); + if (mm_common_band_is_eutran (band)) { + band_4g_found = TRUE; + if (!(allowed_modes & MM_MODEM_MODE_4G)) + g_array_append_val (unapplied_bands, band); + } + if (mm_common_band_is_utran (band)) { + band_3g_found = TRUE; + if (!(allowed_modes & MM_MODEM_MODE_3G)) + g_array_append_val (unapplied_bands, band); + } + if (mm_common_band_is_gsm (band)) { + band_2g_found = TRUE; + if (!(allowed_modes & MM_MODEM_MODE_2G)) + g_array_append_val (unapplied_bands, band); + } + } + + /* If 2G selected, there must be at least one 2G band */ + if ((allowed_modes & MM_MODEM_MODE_2G) && !band_2g_found) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, + "At least one GSM band is required when 2G mode is allowed"); + goto out; + } + + /* If 3G selected, there must be at least one 3G band */ + if ((allowed_modes & MM_MODEM_MODE_3G) && !band_3g_found) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, + "At least one UTRAN band is required when 3G mode is allowed"); + goto out; + } + + /* If 4G selected, there must be at least one 4G band */ + if ((allowed_modes & MM_MODEM_MODE_4G) && !band_4g_found) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, + "At least one E-UTRAN band is required when 4G mode is allowed"); + goto out; + } + + /* Don't try to modify bands for modes that are not enabled */ + if (unapplied_bands->len > 0) { + gchar *str; + + str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)unapplied_bands->data, unapplied_bands->len); + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, + "Cannot update bands for modes not currently allowed: %s", str); + g_free (str); + goto out; + } + +out: + if (unapplied_bands) + g_array_unref (unapplied_bands); + + if (inner_error) { + g_propagate_error (error, inner_error); + return NULL; + } + + return mm_xmm_build_xact_set_command (NULL, bands_array, error); +} + +void +mm_shared_xmm_set_current_bands (MMIfaceModem *self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *command = NULL; + GError *error = NULL; + Private *priv; + + task = g_task_new (self, NULL, callback, user_data); + + /* Setting bands requires additional validation rules based on the + * currently selected list of allowed modes */ + priv = get_private (MM_SHARED_XMM (self)); + if (priv->allowed_modes == MM_MODEM_MODE_NONE) { + error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Cannot set bands if allowed modes are unknown"); + goto out; + } + + command = validate_and_build_command_set_current_bands (MM_SHARED_XMM (self), + bands_array, + priv->supported_modes, + priv->allowed_modes, + &error); + +out: + if (!command) { + g_assert (error); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 10, + FALSE, + (GAsyncReadyCallback)xact_set_bands_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Power state loading (Modem interface) */ + +MMModemPowerState +mm_shared_xmm_load_power_state_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + guint state; + const gchar *response; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return MM_MODEM_POWER_STATE_UNKNOWN; + + if (!mm_3gpp_parse_cfun_query_response (response, &state, error)) + return MM_MODEM_POWER_STATE_UNKNOWN; + + switch (state) { + case 1: + return MM_MODEM_POWER_STATE_ON; + case 4: + return MM_MODEM_POWER_STATE_LOW; + default: + break; + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown +CFUN state: %u", state); + return MM_MODEM_POWER_STATE_UNKNOWN; +} + +void +mm_shared_xmm_load_power_state (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Modem power up/down/off (Modem interface) */ + +static gboolean +common_modem_power_operation_finish (MMSharedXmm *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +power_operation_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +common_modem_power_operation (MMSharedXmm *self, + const gchar *command, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + command, + 30, + FALSE, + (GAsyncReadyCallback) power_operation_ready, + task); +} + +gboolean +mm_shared_xmm_reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error); +} + +void +mm_shared_xmm_reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=16", callback, user_data); +} + +gboolean +mm_shared_xmm_power_off_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error); +} + +void +mm_shared_xmm_power_off (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_modem_power_operation (MM_SHARED_XMM (self), "+CPWROFF", callback, user_data); +} + +gboolean +mm_shared_xmm_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error); +} + +void +mm_shared_xmm_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=4", callback, user_data); +} + +gboolean +mm_shared_xmm_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error); +} + +void +mm_shared_xmm_power_up (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=1", callback, user_data); +} + +/*****************************************************************************/ +/* Check support (Signal interface) */ + +gboolean +mm_shared_xmm_signal_check_support_finish (MMIfaceModemSignal *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +void +mm_shared_xmm_signal_check_support (MMIfaceModemSignal *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+XCESQ=?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load extended signal information (Signal interface) */ + +gboolean +mm_shared_xmm_signal_load_values_finish (MMIfaceModemSignal *self, + GAsyncResult *res, + MMSignal **cdma, + MMSignal **evdo, + MMSignal **gsm, + MMSignal **umts, + MMSignal **lte, + MMSignal **nr5g, + GError **error) +{ + const gchar *response; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response || !mm_xmm_xcesq_response_to_signal_info (response, self, gsm, umts, lte, error)) + return FALSE; + + if (cdma) + *cdma = NULL; + if (evdo) + *evdo = NULL; + if (nr5g) + *nr5g = NULL; + + return TRUE; +} + +void +mm_shared_xmm_signal_load_values (MMIfaceModemSignal *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+XCESQ?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Get GPS control port (Location interface) + * + * This port is an AT port that will also be used for NMEA data. + */ + +static MMPortSerialAt * +shared_xmm_get_gps_control_port (MMSharedXmm *self, + GError **error) +{ + MMPortSerialAt *gps_port = NULL; + + gps_port = mm_base_modem_get_port_gps_control (MM_BASE_MODEM (self)); + if (!gps_port) { + gps_port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); + if (!gps_port) + gps_port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); + } + + if (!gps_port) + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No valid port found to control GPS"); + return gps_port; +} + +/*****************************************************************************/ +/* Load capabilities (Location interface) */ + +MMModemLocationSource +mm_shared_xmm_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +xlcslsr_test_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + const gchar *response; + GError *error = NULL; + Private *priv; + gboolean transport_protocol_invalid_supported; + gboolean transport_protocol_supl_supported; + gboolean standalone_position_mode_supported; + gboolean ms_assisted_based_position_mode_supported; + gboolean loc_response_type_nmea_supported; + gboolean gnss_type_gps_glonass_supported; + + priv = get_private (MM_SHARED_XMM (self)); + + /* Recover parent sources */ + sources = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response || + !mm_xmm_parse_xlcslsr_test_response (response, + &transport_protocol_invalid_supported, + &transport_protocol_supl_supported, + &standalone_position_mode_supported, + &ms_assisted_based_position_mode_supported, + &loc_response_type_nmea_supported, + &gnss_type_gps_glonass_supported, + &error)) { + mm_obj_dbg (self, "XLCSLSR based GPS control unsupported: %s", error->message); + g_clear_error (&error); + } else if (!transport_protocol_invalid_supported || + !standalone_position_mode_supported || + !loc_response_type_nmea_supported || + !gnss_type_gps_glonass_supported) { + mm_obj_dbg (self, "XLCSLSR based GPS control unsupported: protocol invalid %s, standalone %s, nmea %s, gps/glonass %s", + transport_protocol_invalid_supported ? "supported" : "unsupported", + standalone_position_mode_supported ? "supported" : "unsupported", + loc_response_type_nmea_supported ? "supported" : "unsupported", + gnss_type_gps_glonass_supported ? "supported" : "unsupported"); + } else { + mm_obj_dbg (self, "XLCSLSR based GPS control supported"); + priv->supported_sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW); + + if (transport_protocol_supl_supported && ms_assisted_based_position_mode_supported) { + mm_obj_dbg (self, "XLCSLSR based A-GPS control supported"); + priv->supported_sources |= (MM_MODEM_LOCATION_SOURCE_AGPS_MSA | MM_MODEM_LOCATION_SOURCE_AGPS_MSB); + } else { + mm_obj_dbg (self, "XLCSLSR based A-GPS control unsupported: protocol supl %s, ms assisted/based %s", + transport_protocol_supl_supported ? "supported" : "unsupported", + ms_assisted_based_position_mode_supported ? "supported" : "unsupported"); + } + + sources |= priv->supported_sources; + } + + g_task_return_int (task, sources); + g_object_unref (task); +} + +static void +run_xlcslsr_test (GTask *task) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (g_task_get_source_object (task)), + "+XLCSLSR=?", + 3, + TRUE, /* allow caching */ + (GAsyncReadyCallback)xlcslsr_test_ready, + task); +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_XMM (self)); + + sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* If parent already supports GPS sources, we won't do anything else */ + if (sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + mm_obj_dbg (self, "no need to run XLCSLSR based location gathering"); + g_task_return_int (task, sources); + g_object_unref (task); + return; + } + + /* Cache sources supported by the parent */ + g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL); + run_xlcslsr_test (task); +} + +void +mm_shared_xmm_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + priv = get_private (MM_SHARED_XMM (self)); + task = g_task_new (self, NULL, callback, user_data); + + g_assert (priv->iface_modem_location_parent); + + if (!priv->iface_modem_location_parent->load_capabilities || + !priv->iface_modem_location_parent->load_capabilities_finish) { + /* no parent capabilities */ + g_task_set_task_data (task, GUINT_TO_POINTER (MM_MODEM_LOCATION_SOURCE_NONE), NULL); + run_xlcslsr_test (task); + return; + } + + priv->iface_modem_location_parent->load_capabilities (self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + task); +} + +/*****************************************************************************/ +/* NMEA trace processing */ + +static void +nmea_received (MMPortSerialAt *port, + GMatchInfo *info, + MMSharedXmm *self) +{ + gchar *trace; + + trace = g_match_info_fetch (info, 1); + mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), trace); + g_free (trace); +} + +/*****************************************************************************/ +/* GPS engine state selection */ + +#define GPS_ENGINE_STOP_TIMEOUT_SECS 10 + +typedef struct { + GpsEngineState state; + guint engine_stop_timeout_id; +} GpsEngineSelectContext; + +static void +gps_engine_select_context_free (GpsEngineSelectContext *ctx) +{ + g_assert (!ctx->engine_stop_timeout_id); + g_slice_free (GpsEngineSelectContext, ctx); +} + +static gboolean +gps_engine_state_select_finish (MMSharedXmm *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +xlcslsr_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GpsEngineSelectContext *ctx; + const gchar *response; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_XMM (self)); + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_full_finish (self, res, &error); + if (!response) { + g_clear_object (&priv->gps_port); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "GPS engine started"); + + g_assert (priv->gps_port); + mm_port_serial_at_add_unsolicited_msg_handler (priv->gps_port, + priv->nmea_regex, + (MMPortSerialAtUnsolicitedMsgFn)nmea_received, + self, + NULL); + priv->gps_engine_state = ctx->state; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +gps_engine_start (GTask *task) +{ + GpsEngineSelectContext *ctx; + MMSharedXmm *self; + Private *priv; + GError *error = NULL; + guint transport_protocol = 0; + guint pos_mode = 0; + gchar *cmd; + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + g_assert (!priv->gps_port); + priv->gps_port = shared_xmm_get_gps_control_port (self, &error); + if (!priv->gps_port) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + switch (ctx->state) { + case GPS_ENGINE_STATE_STANDALONE: + transport_protocol = 2; + pos_mode = 3; + break; + case GPS_ENGINE_STATE_AGPS_MSB: + transport_protocol = 1; + pos_mode = 1; + break; + case GPS_ENGINE_STATE_AGPS_MSA: + transport_protocol = 1; + pos_mode = 2; + break; + case GPS_ENGINE_STATE_OFF: + default: + g_assert_not_reached (); + break; + } + + mm_obj_dbg (self, "starting GPS engine..."); + + /* + * AT+XLCSLSR + * transport_protocol: 2 (invalid) or 1 (supl) + * pos_mode: 3 (standalone), 1 (msb) or 2 (msa) + * client_id: <empty> + * client_id_type: <empty> + * mlc_number: <empty> + * mlc_number_type: <empty> + * interval: 1 (seconds) + * service_type_id: <empty> + * pseudonym_indicator: <empty> + * loc_response_type: 1 (NMEA strings) + * nmea_mask: 118 (01110110: GGA,GSA,GSV,RMC,VTG) + * gnss_type: 0 (GPS or GLONASS) + */ + g_assert (priv->gps_port); + cmd = g_strdup_printf ("AT+XLCSLSR=%u,%u,,,,,1,,,1,118,0", transport_protocol, pos_mode); + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + priv->gps_port, + cmd, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)xlcslsr_ready, + task); + g_free (cmd); +} + +static GTask * +recover_pending_gps_engine_stop_task (Private *priv) +{ + GTask *task; + GpsEngineSelectContext *ctx; + + /* We're taking over full ownership of the GTask at this point. */ + if (!priv->pending_gps_engine_stop_task) + return NULL; + task = g_steal_pointer (&priv->pending_gps_engine_stop_task); + ctx = g_task_get_task_data (task); + + /* remove timeout */ + if (ctx->engine_stop_timeout_id) { + g_source_remove (ctx->engine_stop_timeout_id); + ctx->engine_stop_timeout_id = 0; + } + + /* disable urc handling */ + mm_port_serial_at_add_unsolicited_msg_handler ( + priv->gps_port, + priv->xlsrstop_regex, + NULL, NULL, NULL); + + return task; +} + +static void +gps_engine_stopped (GTask *task) +{ + MMSharedXmm *self; + GpsEngineSelectContext *ctx; + Private *priv; + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + g_assert (priv->gps_port); + mm_port_serial_at_add_unsolicited_msg_handler ( + priv->gps_port, + priv->nmea_regex, + NULL, NULL, NULL); + g_clear_object (&priv->gps_port); + priv->gps_engine_state = GPS_ENGINE_STATE_OFF; + + /* If already reached requested state, we're done */ + if (ctx->state == priv->gps_engine_state) { + /* If we had an error when requesting this specific state, report it */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Otherwise, start with new state */ + gps_engine_start (task); +} + +static gboolean +xlsrstop_urc_timeout (MMSharedXmm *self) +{ + GTask *task; + Private *priv; + + priv = get_private (self); + + task = recover_pending_gps_engine_stop_task (priv); + g_assert (task); + + mm_obj_dbg (self, "timed out waiting for full GPS engine stop report, assuming stopped..."); + gps_engine_stopped (task); + + return G_SOURCE_REMOVE; +} + +static void +xlsrstop_urc_received (MMPortSerialAt *port, + GMatchInfo *info, + MMSharedXmm *self) +{ + GTask *task; + Private *priv; + + priv = get_private (self); + + task = recover_pending_gps_engine_stop_task (priv); + g_assert (task); + + mm_obj_dbg (self, "GPS engine fully stopped"); + gps_engine_stopped (task); +} + +static void +xlsrstop_ready (MMBaseModem *self, + GAsyncResult *res) +{ + g_autoptr(GError) error = NULL; + + if (!mm_base_modem_at_command_full_finish (self, res, &error)) { + Private *priv; + GTask *task; + + mm_obj_dbg (self, "GPS engine stop request failed: %s", error->message); + + priv = get_private (MM_SHARED_XMM (self)); + task = recover_pending_gps_engine_stop_task (priv); + if (task) { + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); + } + return; + } + + mm_obj_dbg (self, "GPS engine stop request accepted"); +} + +static void +gps_engine_stop (GTask *task) +{ + MMSharedXmm *self; + Private *priv; + GpsEngineSelectContext *ctx; + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + g_assert (priv->gps_port); + + /* After a +XLSRSTOP command the modem will reply OK to report that the stop + * request has been received, but at this point the engine may still be on. + * We must wait for the additional +XLSRSTOP URC to tell us that the engine + * is really off before going on. + * + * We do this by setting up a temporary regex match for the URC during this + * operation, and also by configuring a timeout so that we don't wait forever + * for the URC. + * + * The initial +XLSRSTOP response will be ignored unless an error is being + * reported. + * + * The operation task is kept in private info because it will be shared by all + * the possible paths that may complete it (response, URC, timeout). + */ + if (priv->pending_gps_engine_stop_task) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "An engine stop task is already ongoing"); + g_object_unref (task); + return; + } + priv->pending_gps_engine_stop_task = task; + + mm_obj_dbg (self, "launching GPS engine stop operation..."); + + ctx->engine_stop_timeout_id = g_timeout_add_seconds (GPS_ENGINE_STOP_TIMEOUT_SECS, + (GSourceFunc) xlsrstop_urc_timeout, + self); + + mm_port_serial_at_add_unsolicited_msg_handler ( + priv->gps_port, + priv->xlsrstop_regex, + (MMPortSerialAtUnsolicitedMsgFn)xlsrstop_urc_received, + self, + NULL); + + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + priv->gps_port, + "+XLSRSTOP", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)xlsrstop_ready, + NULL); +} + +static void +gps_engine_state_select (MMSharedXmm *self, + GpsEngineState state, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GpsEngineSelectContext *ctx; + GTask *task; + Private *priv; + + priv = get_private (self); + + task = g_task_new (self, NULL, callback, user_data); + ctx = g_slice_new0 (GpsEngineSelectContext); + ctx->state = state; + g_task_set_task_data (task, ctx, (GDestroyNotify)gps_engine_select_context_free); + + /* If already in the requested state, we're done */ + if (state == priv->gps_engine_state) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* If states are different we always STOP first */ + if (priv->gps_engine_state != GPS_ENGINE_STATE_OFF) { + gps_engine_stop (task); + return; + } + + /* If GPS already stopped, go on to START right away */ + g_assert (state != GPS_ENGINE_STATE_OFF); + gps_engine_start (task); +} + +static GpsEngineState +gps_engine_state_get_expected (MMModemLocationSource sources) +{ + /* If at lease one of GPS nmea/raw sources enabled, engine started */ + if (sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + /* If MSA A-GPS is enabled, MSA mode */ + if (sources & MM_MODEM_LOCATION_SOURCE_AGPS_MSA) + return GPS_ENGINE_STATE_AGPS_MSA; + /* If MSB A-GPS is enabled, MSB mode */ + if (sources & MM_MODEM_LOCATION_SOURCE_AGPS_MSB) + return GPS_ENGINE_STATE_AGPS_MSB; + /* Otherwise, STANDALONE */ + return GPS_ENGINE_STATE_STANDALONE; + } + /* If no GPS nmea/raw sources enabled, engine stopped */ + return GPS_ENGINE_STATE_OFF; +} + +/*****************************************************************************/ +/* Disable location gathering (Location interface) */ + +gboolean +mm_shared_xmm_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +disable_gps_engine_state_select_ready (MMSharedXmm *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource source; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_XMM (self)); + + if (!gps_engine_state_select_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + source = GPOINTER_TO_UINT (g_task_get_task_data (task)); + priv->enabled_sources &= ~source; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_disable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_XMM (self)); + + g_assert (priv->iface_modem_location_parent); + if (!priv->iface_modem_location_parent->disable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_xmm_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); + + priv = get_private (MM_SHARED_XMM (self)); + g_assert (priv->iface_modem_location_parent); + + /* Only consider request if it applies to one of the sources we are + * supporting, otherwise run parent disable */ + if (!(priv->supported_sources & source)) { + /* If disabling implemented by the parent, run it. */ + if (priv->iface_modem_location_parent->disable_location_gathering && + priv->iface_modem_location_parent->disable_location_gathering_finish) { + priv->iface_modem_location_parent->disable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_disable_location_gathering_ready, + task); + return; + } + /* Otherwise, we're done */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* We only expect GPS sources here */ + g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_AGPS_MSA | + MM_MODEM_LOCATION_SOURCE_AGPS_MSB)); + + /* Update engine based on the expected sources */ + gps_engine_state_select (MM_SHARED_XMM (self), + gps_engine_state_get_expected (priv->enabled_sources & ~source), + (GAsyncReadyCallback) disable_gps_engine_state_select_ready, + task); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +gboolean +mm_shared_xmm_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +enable_gps_engine_state_select_ready (MMSharedXmm *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource source; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_XMM (self)); + + if (!gps_engine_state_select_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + source = GPOINTER_TO_UINT (g_task_get_task_data (task)); + priv->enabled_sources |= source; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_XMM (self)); + + g_assert (priv->iface_modem_location_parent); + if (!priv->iface_modem_location_parent->enable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_xmm_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); + + priv = get_private (MM_SHARED_XMM (self)); + g_assert (priv->iface_modem_location_parent); + + /* Only consider request if it applies to one of the sources we are + * supporting, otherwise run parent enable */ + if (priv->iface_modem_location_parent->enable_location_gathering && + priv->iface_modem_location_parent->enable_location_gathering_finish && + !(priv->supported_sources & source)) { + priv->iface_modem_location_parent->enable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); + return; + } + + /* We only expect GPS sources here */ + g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_AGPS_MSA | + MM_MODEM_LOCATION_SOURCE_AGPS_MSB)); + + /* Update engine based on the expected sources */ + gps_engine_state_select (MM_SHARED_XMM (self), + gps_engine_state_get_expected (priv->enabled_sources | source), + (GAsyncReadyCallback) enable_gps_engine_state_select_ready, + task); +} + +/*****************************************************************************/ +/* Location: Load SUPL server */ + +gchar * +mm_shared_xmm_location_load_supl_server_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +xlcsslp_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + gchar *supl_address; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response || !mm_xmm_parse_xlcsslp_query_response (response, &supl_address, &error)) + g_task_return_error (task, error); + else + g_task_return_pointer (task, supl_address, g_free); + g_object_unref (task); +} + +void +mm_shared_xmm_location_load_supl_server (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+XLCSSLP?", + 3, + FALSE, + (GAsyncReadyCallback)xlcsslp_query_ready, + task); +} + +/*****************************************************************************/ + +gboolean +mm_shared_xmm_location_set_supl_server_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +xlcsslp_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_xmm_location_set_supl_server (MMIfaceModemLocation *self, + const gchar *supl, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *cmd = NULL; + gchar *fqdn = NULL; + guint32 ip; + guint16 port; + + task = g_task_new (self, NULL, callback, user_data); + + mm_parse_supl_address (supl, &fqdn, &ip, &port, NULL); + g_assert (port); + if (fqdn) + cmd = g_strdup_printf ("+XLCSSLP=1,%s,%u", fqdn, port); + else if (ip) { + struct in_addr a = { .s_addr = ip }; + gchar buf[INET_ADDRSTRLEN + 1] = { 0 }; + + /* we got 'ip' from inet_pton(), so this next step should always succeed */ + g_assert (inet_ntop (AF_INET, &a, buf, sizeof (buf) - 1)); + cmd = g_strdup_printf ("+XLCSSLP=0,%s,%u", buf, port); + } else + g_assert_not_reached (); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 3, + FALSE, + (GAsyncReadyCallback)xlcsslp_set_ready, + task); + g_free (cmd); + g_free (fqdn); +} + +/*****************************************************************************/ + +void +mm_shared_xmm_setup_ports (MMBroadbandModem *self) +{ + Private *priv; + g_autoptr(MMPortSerialAt) gps_port = NULL; + + priv = get_private (MM_SHARED_XMM (self)); + g_assert (priv->broadband_modem_class_parent); + g_assert (priv->broadband_modem_class_parent->setup_ports); + + /* Parent setup first always */ + priv->broadband_modem_class_parent->setup_ports (self); + + /* Then, setup the GPS port */ + gps_port = shared_xmm_get_gps_control_port (MM_SHARED_XMM (self), NULL); + if (gps_port) { + /* After running AT+XLSRSTOP we may get an unsolicited response + * reporting its status, we just ignore it. */ + mm_port_serial_at_add_unsolicited_msg_handler ( + gps_port, + priv->xlsrstop_regex, + NULL, NULL, NULL); + + /* make sure GPS is stopped in case it was left enabled */ + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + gps_port, + "+XLSRSTOP", + 3, FALSE, FALSE, NULL, NULL, NULL); + } +} + +/*****************************************************************************/ + +static void +shared_xmm_init (gpointer g_iface) +{ +} + +GType +mm_shared_xmm_get_type (void) +{ + static GType shared_xmm_type = 0; + + if (!G_UNLIKELY (shared_xmm_type)) { + static const GTypeInfo info = { + sizeof (MMSharedXmm), /* class_size */ + shared_xmm_init, /* base_init */ + NULL, /* base_finalize */ + }; + + shared_xmm_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedXmm", &info, 0); + g_type_interface_add_prerequisite (shared_xmm_type, MM_TYPE_IFACE_MODEM); + g_type_interface_add_prerequisite (shared_xmm_type, MM_TYPE_IFACE_MODEM_LOCATION); + } + + return shared_xmm_type; +} diff --git a/src/plugins/xmm/mm-shared-xmm.h b/src/plugins/xmm/mm-shared-xmm.h new file mode 100644 index 00000000..a1f51639 --- /dev/null +++ b/src/plugins/xmm/mm-shared-xmm.h @@ -0,0 +1,184 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_SHARED_XMM_H +#define MM_SHARED_XMM_H + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-signal.h" +#include "mm-iface-modem-location.h" + +#define MM_TYPE_SHARED_XMM (mm_shared_xmm_get_type ()) +#define MM_SHARED_XMM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_XMM, MMSharedXmm)) +#define MM_IS_SHARED_XMM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_XMM)) +#define MM_SHARED_XMM_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_XMM, MMSharedXmm)) + +typedef struct _MMSharedXmm MMSharedXmm; + +struct _MMSharedXmm { + GTypeInterface g_iface; + + /* Peek broadband modem class of the parent class of the object */ + MMBroadbandModemClass * (* peek_parent_broadband_modem_class) (MMSharedXmm *self); + + /* Peek location interface of the parent class of the object */ + MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedXmm *self); +}; + +GType mm_shared_xmm_get_type (void); + +/* Shared XMM device setup */ + +void mm_shared_xmm_setup_ports (MMBroadbandModem *self); + +/* Shared XMM device management support */ + +void mm_shared_xmm_load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +GArray *mm_shared_xmm_load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); +void mm_shared_xmm_load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_xmm_load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error); +void mm_shared_xmm_set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_xmm_set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_shared_xmm_load_supported_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +GArray *mm_shared_xmm_load_supported_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); +void mm_shared_xmm_load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +GArray *mm_shared_xmm_load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); +void mm_shared_xmm_set_current_bands (MMIfaceModem *self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_xmm_set_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_shared_xmm_load_power_state (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMModemPowerState mm_shared_xmm_load_power_state_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); +void mm_shared_xmm_power_up (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_xmm_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); +void mm_shared_xmm_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_xmm_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); +void mm_shared_xmm_power_off (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_xmm_power_off_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); +void mm_shared_xmm_reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_xmm_reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +gboolean mm_shared_xmm_signal_check_support_finish (MMIfaceModemSignal *self, + GAsyncResult *res, + GError **error); + +void mm_shared_xmm_signal_check_support (MMIfaceModemSignal *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_xmm_signal_load_values_finish (MMIfaceModemSignal *self, + GAsyncResult *res, + MMSignal **cdma, + MMSignal **evdo, + MMSignal **gsm, + MMSignal **umts, + MMSignal **lte, + MMSignal **nr5g, + GError **error); +void mm_shared_xmm_signal_load_values (MMIfaceModemSignal *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +void mm_shared_xmm_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMModemLocationSource mm_shared_xmm_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_xmm_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_xmm_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_xmm_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_xmm_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_xmm_location_load_supl_server (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data); +gchar *mm_shared_xmm_location_load_supl_server_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_xmm_location_set_supl_server (MMIfaceModemLocation *self, + const gchar *supl, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_xmm_location_set_supl_server_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); + +#endif /* MM_SHARED_XMM_H */ diff --git a/src/plugins/xmm/mm-shared.c b/src/plugins/xmm/mm-shared.c new file mode 100644 index 00000000..203f0fbb --- /dev/null +++ b/src/plugins/xmm/mm-shared.c @@ -0,0 +1,20 @@ +/* -*- 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 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-shared.h" + +MM_SHARED_DEFINE_MAJOR_VERSION +MM_SHARED_DEFINE_MINOR_VERSION +MM_SHARED_DEFINE_NAME(Xmm) diff --git a/src/plugins/xmm/tests/test-modem-helpers-xmm.c b/src/plugins/xmm/tests/test-modem-helpers-xmm.c new file mode 100644 index 00000000..e40ffcab --- /dev/null +++ b/src/plugins/xmm/tests/test-modem-helpers-xmm.c @@ -0,0 +1,780 @@ +/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> +#include <math.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-xmm.h" + +#define g_assert_cmpfloat_tolerance(val1, val2, tolerance) \ + g_assert_cmpfloat (fabs (val1 - val2), <, tolerance) + +/*****************************************************************************/ +/* Test XACT=? responses */ + +static void +validate_xact_test_response (const gchar *response, + const MMModemModeCombination *expected_modes, + guint n_expected_modes, + const MMModemBand *expected_bands, + guint n_expected_bands) +{ + GError *error = NULL; + GArray *modes = NULL; + GArray *bands = NULL; + gboolean ret; + guint i; + + ret = mm_xmm_parse_xact_test_response (response, NULL, &modes, &bands, &error); + g_assert_no_error (error); + g_assert (ret); + + g_assert_cmpuint (modes->len, ==, n_expected_modes); + for (i = 0; i < modes->len; i++) { + MMModemModeCombination mode; + guint j; + gboolean found = FALSE; + + mode = g_array_index (modes, MMModemModeCombination, i); + for (j = 0; !found && j < n_expected_modes; j++) + found = (mode.allowed == expected_modes[j].allowed && mode.preferred == expected_modes[j].preferred); + g_assert (found); + } + g_array_unref (modes); + + g_assert_cmpuint (bands->len, ==, n_expected_bands); + for (i = 0; i < bands->len; i++) { + MMModemBand band; + guint j; + gboolean found = FALSE; + + band = g_array_index (bands, MMModemBand, i); + for (j = 0; !found && j < n_expected_bands; j++) + found = (band == expected_bands[j]); + g_assert (found); + } + g_array_unref (bands); +} + +static void +test_xact_test_4g_only (void) +{ + const gchar *response = + "+XACT: " + "(0-6),(0-2),0," + "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166"; + + static const MMModemModeCombination expected_modes[] = { + { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + }; + + static const MMModemBand expected_bands[] = { + MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21, + MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38, + MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66 + }; + + /* NOTE: 2G and 3G modes are reported in XACT but no 2G or 3G frequencies supported */ + validate_xact_test_response (response, + expected_modes, G_N_ELEMENTS (expected_modes), + expected_bands, G_N_ELEMENTS (expected_bands)); +} + +static void +test_xact_test_3g_4g (void) +{ + const gchar *response = + "+XACT: " + "(0-6),(0-2),0," + "1,2,4,5,8," + "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166"; + + static const MMModemModeCombination expected_modes[] = { + { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G }, + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G }, + }; + + static const MMModemBand expected_bands[] = { + MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21, + MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38, + MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66 + }; + + /* NOTE: 2G modes are reported in XACT but no 2G frequencies supported */ + validate_xact_test_response (response, + expected_modes, G_N_ELEMENTS (expected_modes), + expected_bands, G_N_ELEMENTS (expected_bands)); +} + +static void +test_xact_test_2g_3g_4g (void) +{ + const gchar *response = + "+XACT: " + "(0-6),(0-2),0," + "900,1800,1900,850," + "1,2,4,5,8," + "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166"; + + static const MMModemModeCombination expected_modes[] = { + { MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_2G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_2G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G }, + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G }, + { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_2G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G }, + { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G }, + }; + + static const MMModemBand expected_bands[] = { + MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS, MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21, + MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38, + MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66 + }; + + validate_xact_test_response (response, + expected_modes, G_N_ELEMENTS (expected_modes), + expected_bands, G_N_ELEMENTS (expected_bands)); +} + +/*****************************************************************************/ +/* Test XACT? responses */ + +static void +validate_xact_query_response (const gchar *response, + const MMModemModeCombination *expected_mode, + const MMModemBand *expected_bands, + guint n_expected_bands) +{ + GError *error = NULL; + GArray *bands = NULL; + gboolean ret; + guint i; + + MMModemModeCombination mode = { + .allowed = MM_MODEM_MODE_NONE, + .preferred = MM_MODEM_MODE_NONE, + }; + + ret = mm_xmm_parse_xact_query_response (response, &mode, &bands, &error); + g_assert_no_error (error); + g_assert (ret); + + g_assert_cmpuint (mode.allowed, ==, expected_mode->allowed); + g_assert_cmpuint (mode.preferred, ==, expected_mode->preferred); + + g_assert_cmpuint (bands->len, ==, n_expected_bands); + for (i = 0; i < bands->len; i++) { + MMModemBand band; + guint j; + gboolean found = FALSE; + + band = g_array_index (bands, MMModemBand, i); + for (j = 0; !found && j < n_expected_bands; j++) + found = (band == expected_bands[j]); + g_assert (found); + } + g_array_unref (bands); +} + +static void +test_xact_query_3g_only (void) +{ + const gchar *response = + "+XACT: " + "1,1,," + "1,2,4,5,8," + "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166"; + + static const MMModemModeCombination expected_mode = { + .allowed = MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE + }; + + static const MMModemBand expected_bands[] = { + MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21, + MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38, + MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66 + }; + + validate_xact_query_response (response, + &expected_mode, + expected_bands, G_N_ELEMENTS (expected_bands)); +} + +static void +test_xact_query_3g_4g (void) +{ + const gchar *response = + "+XACT: " + "4,1,2," + "1,2,4,5,8," + "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166"; + + static const MMModemModeCombination expected_mode = { + .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_3G + }; + + static const MMModemBand expected_bands[] = { + MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21, + MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38, + MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66 + }; + + validate_xact_query_response (response, + &expected_mode, + expected_bands, G_N_ELEMENTS (expected_bands)); +} + +/*****************************************************************************/ + +#define XACT_SET_TEST_MAX_BANDS 6 + +typedef struct { + MMModemMode allowed; + MMModemMode preferred; + MMModemBand bands[XACT_SET_TEST_MAX_BANDS]; + const gchar *expected_command; +} XactSetTest; + +static const XactSetTest set_tests[] = { + { + /* 2G-only, no explicit bands */ + .allowed = MM_MODEM_MODE_2G, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=0,," + }, + { + /* 3G-only, no explicit bands */ + .allowed = MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=1,," + }, + { + /* 4G-only, no explicit bands */ + .allowed = MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=2,," + }, + { + /* 2G+3G, none preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=3,," + }, + { + /* 2G+3G, 2G preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_2G, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=3,0," + }, + { + /* 2G+3G, 3G preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .preferred = MM_MODEM_MODE_3G, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=3,1," + }, + { + /* 3G+4G, none preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=4,," + }, + { + /* 3G+4G, 3G preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_3G, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=4,1," + }, + { + /* 3G+4G, 4G preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_4G, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=4,2," + }, + { + /* 2G+4G, none preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=5,," + }, + { + /* 2G+4G, 2G preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_2G, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=5,0," + }, + { + /* 2G+4G, 4G preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_4G, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=5,2," + }, + { + /* 2G+3G+4G, none preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=6,," + }, + { + /* 2G+3G+4G, 2G preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_2G, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=6,0," + }, + { + /* 2G+3G+4G, 3G preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_3G, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=6,1," + }, + { + /* 2G+3G+4G, 4G preferred, no explicit bands */ + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_4G, + .bands = { [0] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=6,2," + }, + { + /* 2G bands, no explicit modes */ + .allowed = MM_MODEM_MODE_NONE, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_EGSM, + [1] = MM_MODEM_BAND_DCS, + [2] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=,,,900,1800" + }, + { + /* 3G bands, no explicit modes */ + .allowed = MM_MODEM_MODE_NONE, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_UTRAN_1, + [1] = MM_MODEM_BAND_UTRAN_2, + [2] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=,,,1,2" + }, + { + /* 4G bands, no explicit modes */ + .allowed = MM_MODEM_MODE_NONE, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_EUTRAN_1, + [1] = MM_MODEM_BAND_EUTRAN_2, + [2] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=,,,101,102" + }, + { + /* 2G, 3G and 4G bands, no explicit modes */ + .allowed = MM_MODEM_MODE_NONE, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_EGSM, + [1] = MM_MODEM_BAND_DCS, + [2] = MM_MODEM_BAND_UTRAN_1, + [3] = MM_MODEM_BAND_UTRAN_2, + [4] = MM_MODEM_BAND_EUTRAN_1, + [5] = MM_MODEM_BAND_EUTRAN_2 }, + .expected_command = "+XACT=,,,900,1800,1,2,101,102" + }, + { + /* Auto bands, no explicit modes */ + .allowed = MM_MODEM_MODE_NONE, + .preferred = MM_MODEM_MODE_NONE, + .bands = { [0] = MM_MODEM_BAND_ANY, + [1] = MM_MODEM_BAND_UNKNOWN }, + .expected_command = "+XACT=,,,0" + }, + + { + /* 2G+3G+4G with 4G preferred, and 2G+3G+4G bands */ + .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .preferred = MM_MODEM_MODE_4G, + .bands = { [0] = MM_MODEM_BAND_EGSM, + [1] = MM_MODEM_BAND_DCS, + [2] = MM_MODEM_BAND_UTRAN_1, + [3] = MM_MODEM_BAND_UTRAN_2, + [4] = MM_MODEM_BAND_EUTRAN_1, + [5] = MM_MODEM_BAND_EUTRAN_2 }, + .expected_command = "+XACT=6,2,,900,1800,1,2,101,102" + }, +}; + +static void +validate_xact_set_command (const MMModemMode allowed, + const MMModemMode preferred, + const MMModemBand *bands, + guint n_bands, + const gchar *expected_command) +{ + gchar *command; + MMModemModeCombination mode; + GArray *bandsarray = NULL; + GError *error = NULL; + + if (n_bands) + bandsarray = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_bands), bands, n_bands); + + mode.allowed = allowed; + mode.preferred = preferred; + + command = mm_xmm_build_xact_set_command ((mode.allowed != MM_MODEM_MODE_NONE) ? &mode : NULL, bandsarray, &error); + g_assert_no_error (error); + g_assert (command); + + g_assert_cmpstr (command, == , expected_command); + + g_free (command); + if (bandsarray) + g_array_unref (bandsarray); +} + +static void +test_xact_set (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (set_tests); i++) { + guint n_bands = 0; + guint j; + + for (j = 0; j < XACT_SET_TEST_MAX_BANDS; j++) { + if (set_tests[i].bands[j] != MM_MODEM_BAND_UNKNOWN) + n_bands++; + } + + validate_xact_set_command (set_tests[i].allowed, + set_tests[i].preferred, + set_tests[i].bands, + n_bands, + set_tests[i].expected_command); + } +} + +/*****************************************************************************/ +/* Test +XCESQ responses */ + +typedef struct { + const gchar *str; + + gboolean gsm_info; + guint rxlev; + gdouble rssi; + guint ber; + + gboolean umts_info; + guint rscp_level; + gdouble rscp; + guint ecn0_level; + gdouble ecio; + + gboolean lte_info; + guint rsrq_level; + gdouble rsrq; + guint rsrp_level; + gdouble rsrp; + gint rssnr_level; + gdouble rssnr; +} XCesqResponseTest; + +static const XCesqResponseTest xcesq_response_tests[] = { + { + .str = "+XCESQ: 0,99,99,255,255,19,46,32", + .gsm_info = FALSE, .rxlev = 99, .ber = 99, + .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255, + .lte_info = TRUE, .rsrq_level = 19, .rsrq = -10.5, .rsrp_level = 46, .rsrp = -95.0, .rssnr_level = 32, .rssnr = 16.0 + }, + { + .str = "+XCESQ: 0,99,99,255,255,19,46,-32", + .gsm_info = FALSE, .rxlev = 99, .ber = 99, + .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255, + .lte_info = TRUE, .rsrq_level = 19, .rsrq = -10.5, .rsrp_level = 46, .rsrp = -95.0, .rssnr_level = -32, .rssnr = -16.0 + }, + { + .str = "+XCESQ: 0,99,99,255,255,16,47,28", + .gsm_info = FALSE, .rxlev = 99, .ber = 99, + .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255, + .lte_info = TRUE, .rsrq_level = 16, .rsrq = -12.0, .rsrp_level = 47, .rsrp = -94.0, .rssnr_level = 28, .rssnr = 14.0 + }, + { + .str = "+XCESQ: 0,99,99,41,29,255,255,255", + .gsm_info = FALSE, .rxlev = 99, .ber = 99, + .umts_info = TRUE, .rscp_level = 41, .rscp = -80.0, .ecn0_level = 29, .ecio = -10.0, + .lte_info = FALSE, .rsrq_level = 255, .rsrp_level = 255, .rssnr_level = 255 + }, + { + .str = "+XCESQ: 0,10,6,255,255,255,255,255", + .gsm_info = TRUE, .rxlev = 10, .rssi = -101.0, .ber = 6, + .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255, + .lte_info = FALSE, .rsrq_level = 255, .rsrp_level = 255, .rssnr_level = 255 + } +}; + +static void +test_xcesq_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (xcesq_response_tests); i++) { + GError *error = NULL; + gboolean success; + guint rxlev = G_MAXUINT; + guint ber = G_MAXUINT; + guint rscp = G_MAXUINT; + guint ecn0 = G_MAXUINT; + guint rsrq = G_MAXUINT; + guint rsrp = G_MAXUINT; + gint rssnr = G_MAXUINT; + + success = mm_xmm_parse_xcesq_query_response (xcesq_response_tests[i].str, + &rxlev, &ber, + &rscp, &ecn0, + &rsrq, &rsrp, + &rssnr, &error); + g_assert_no_error (error); + g_assert (success); + + g_assert_cmpuint (xcesq_response_tests[i].rxlev, ==, rxlev); + g_assert_cmpuint (xcesq_response_tests[i].ber, ==, ber); + g_assert_cmpuint (xcesq_response_tests[i].rscp_level, ==, rscp); + g_assert_cmpuint (xcesq_response_tests[i].ecn0_level, ==, ecn0); + g_assert_cmpuint (xcesq_response_tests[i].rsrq_level, ==, rsrq); + g_assert_cmpuint (xcesq_response_tests[i].rsrp_level, ==, rsrp); + g_assert_cmpuint (xcesq_response_tests[i].rssnr_level, ==, rssnr); + } +} + +static void +test_xcesq_response_to_signal (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (xcesq_response_tests); i++) { + GError *error = NULL; + gboolean success; + MMSignal *gsm = NULL; + MMSignal *umts = NULL; + MMSignal *lte = NULL; + + success = mm_xmm_xcesq_response_to_signal_info (xcesq_response_tests[i].str, + NULL, + &gsm, &umts, <e, + &error); + g_assert_no_error (error); + g_assert (success); + + if (xcesq_response_tests[i].gsm_info) { + g_assert (gsm); + g_assert_cmpfloat_tolerance (mm_signal_get_rssi (gsm), xcesq_response_tests[i].rssi, 0.1); + g_object_unref (gsm); + } else + g_assert (!gsm); + + if (xcesq_response_tests[i].umts_info) { + g_assert (umts); + g_assert_cmpfloat_tolerance (mm_signal_get_rscp (umts), xcesq_response_tests[i].rscp, 0.1); + g_assert_cmpfloat_tolerance (mm_signal_get_ecio (umts), xcesq_response_tests[i].ecio, 0.1); + g_object_unref (umts); + } else + g_assert (!umts); + + if (xcesq_response_tests[i].lte_info) { + g_assert (lte); + g_assert_cmpfloat_tolerance (mm_signal_get_rsrq (lte), xcesq_response_tests[i].rsrq, 0.1); + g_assert_cmpfloat_tolerance (mm_signal_get_rsrp (lte), xcesq_response_tests[i].rsrp, 0.1); + g_assert_cmpfloat_tolerance (mm_signal_get_snr (lte), xcesq_response_tests[i].rssnr, 0.1); + g_object_unref (lte); + } else + g_assert (!lte); + } +} + +/*****************************************************************************/ +/* AT+XLCSLSR=? response parser */ + +typedef struct { + const gchar *response; + gboolean expected_transport_protocol_invalid_supported; + gboolean expected_transport_protocol_supl_supported; + gboolean expected_standalone_position_mode_supported; + gboolean expected_ms_assisted_based_position_mode_supported; + gboolean expected_loc_response_type_nmea_supported; + gboolean expected_gnss_type_gps_glonass_supported; +} XlcslsrTest; + +static XlcslsrTest xlcslsr_tests[] = { + { + "+XLCSLSR:(0-2),(0-3), ,(0-1), ,(0-1),(0-7200),(0-255),(0-1),(0-2),(1-256),(0-1)", + TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, + }, + { + "+XLCSLSR:(0,1,2),(0,1,2,3), ,(0,1), ,(0,1),(0-7200),(0-255),(0,1),(0,1,2),(1-256),(0,1)", + TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, + }, + { + "+XLCSLSR:(0-1),(0-2), ,(0,1), ,(0,1),(0 -7200),(0-255),(0-1),(0),(1-256),(1)", + FALSE, TRUE, FALSE, TRUE, FALSE, FALSE + }, +}; + +static void +test_xlcslsr_test (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (xlcslsr_tests); i++) { + GError *error = NULL; + gboolean ret; + gboolean transport_protocol_invalid_supported; + gboolean transport_protocol_supl_supported; + gboolean standalone_position_mode_supported; + gboolean ms_assisted_based_position_mode_supported; + gboolean loc_response_type_nmea_supported; + gboolean gnss_type_gps_glonass_supported; + + ret = mm_xmm_parse_xlcslsr_test_response (xlcslsr_tests[i].response, + &transport_protocol_invalid_supported, + &transport_protocol_supl_supported, + &standalone_position_mode_supported, + &ms_assisted_based_position_mode_supported, + &loc_response_type_nmea_supported, + &gnss_type_gps_glonass_supported, + &error); + g_assert_no_error (error); + g_assert (ret); + + g_assert (transport_protocol_invalid_supported == xlcslsr_tests[i].expected_transport_protocol_invalid_supported); + g_assert (transport_protocol_supl_supported == xlcslsr_tests[i].expected_transport_protocol_supl_supported); + g_assert (standalone_position_mode_supported == xlcslsr_tests[i].expected_standalone_position_mode_supported); + g_assert (ms_assisted_based_position_mode_supported == xlcslsr_tests[i].expected_ms_assisted_based_position_mode_supported); + g_assert (loc_response_type_nmea_supported == xlcslsr_tests[i].expected_loc_response_type_nmea_supported); + g_assert (gnss_type_gps_glonass_supported == xlcslsr_tests[i].expected_gnss_type_gps_glonass_supported); + } +} + +/*****************************************************************************/ +/* AT+XLCSSLP? response parser */ + +typedef struct { + const gchar *response; + const gchar *expected; +} XlcsslpQuery; + +static XlcsslpQuery xlcsslp_queries[] = { + { + "+XLCSSLP:1,\"www.spirent-lcs.com\",7275", + "www.spirent-lcs.com:7275" + }, + { + "+XLCSSLP:0,\"123.123.123.123\",7275", + "123.123.123.123:7275" + }, +}; + +static void +test_xlcsslp_queries (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (xlcsslp_queries); i++) { + GError *error = NULL; + gchar *supl_server = NULL; + gboolean ret; + + ret = mm_xmm_parse_xlcsslp_query_response (xlcsslp_queries[i].response, + &supl_server, + &error); + g_assert_no_error (error); + g_assert (ret); + + g_assert_cmpstr (supl_server, ==, xlcsslp_queries[i].expected); + g_free (supl_server); + } +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/xmm/xact/test/4g-only", test_xact_test_4g_only); + g_test_add_func ("/MM/xmm/xact/test/3g-4g", test_xact_test_3g_4g); + g_test_add_func ("/MM/xmm/xact/test/2g-3g-4g", test_xact_test_2g_3g_4g); + + g_test_add_func ("/MM/xmm/xact/query/3g-only", test_xact_query_3g_only); + g_test_add_func ("/MM/xmm/xact/query/3g-4g", test_xact_query_3g_4g); + + g_test_add_func ("/MM/xmm/xact/set", test_xact_set); + + g_test_add_func ("/MM/xmm/xcesq/query_response", test_xcesq_response); + g_test_add_func ("/MM/xmm/xcesq/query_response_to_signal", test_xcesq_response_to_signal); + + g_test_add_func ("/MM/xmm/xlcslsr/test", test_xlcslsr_test); + + g_test_add_func ("/MM/xmm/xlcsslp/query", test_xlcsslp_queries); + + return g_test_run (); +} diff --git a/src/plugins/zte/77-mm-zte-port-types.rules b/src/plugins/zte/77-mm-zte-port-types.rules new file mode 100644 index 00000000..46a83aca --- /dev/null +++ b/src/plugins/zte/77-mm-zte-port-types.rules @@ -0,0 +1,204 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_zte_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="19d2", GOTO="mm_zte_port_types" +GOTO="mm_zte_port_types_end" + +LABEL="mm_zte_port_types" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0001", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0001", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0002", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0002", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0003", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0003", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0004", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0004", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0005", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0005", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0006", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0006", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0007", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0007", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0008", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0008", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0009", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0009", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="000A", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="000A", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0012", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0012", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0015", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0015", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0016", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0016", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0018", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0018", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0019", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0019", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0021", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0021", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0024", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0024", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0025", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0025", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0030", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0030", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0031", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0031", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0033", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0033", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0037", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0037", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0039", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0039", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0042", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0042", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0043", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0043", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0048", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0048", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0049", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0049", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0052", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0052", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0054", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0054", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0055", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0055", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0057", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0057", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0058", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0058", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0063", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0063", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0064", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0064", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0066", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0066", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0078", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0078", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0082", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0082", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0091", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0091", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0108", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0108", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0113", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0113", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0117", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0117", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0118", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0118", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0121", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0121", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0122", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0122", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0123", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0123", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0124", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0124", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0126", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0126", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0128", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0128", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0156", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0156", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1007", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1007", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1008", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1008", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1254", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1254", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1268", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1268", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1515", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1515", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="2002", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="2002", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="2003", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="2003", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Icera-based devices that use DHCP, not AT%IPDPADDR +ATTRS{product}=="K3805-z", ENV{ID_MM_ZTE_ICERA_DHCP}="1" + +# MF60 exposes QMI, but it is unusable, fallback to AT+PPP +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1402", SUBSYSTEM=="usb", KERNEL=="cdc-wdm*", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1402", SUBSYSTEM=="usbmisc", KERNEL=="cdc-wdm*", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1402", SUBSYSTEM=="net", ENV{ID_MM_PORT_IGNORE}="1" + +LABEL="mm_zte_port_types_end" diff --git a/src/plugins/zte/mm-broadband-modem-zte-icera.c b/src/plugins/zte/mm-broadband-modem-zte-icera.c new file mode 100644 index 00000000..66aea942 --- /dev/null +++ b/src/plugins/zte/mm-broadband-modem-zte-icera.c @@ -0,0 +1,204 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-base-modem-at.h" +#include "mm-common-zte.h" +#include "mm-broadband-modem-zte-icera.h" +#include "mm-modem-helpers.h" + +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); + +static MMIfaceModem3gpp *iface_modem_3gpp_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemZteIcera, mm_broadband_modem_zte_icera, MM_TYPE_BROADBAND_MODEM_ICERA, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)); + +struct _MMBroadbandModemZteIceraPrivate { + /* Unsolicited messaging setup */ + MMCommonZteUnsolicitedSetup *unsolicited_setup; +}; + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self), + MM_BROADBAND_MODEM_ZTE_ICERA (self)->priv->unsolicited_setup, + TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, + task); +} + +static void +parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Our own cleanup first */ + mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self), + MM_BROADBAND_MODEM_ZTE_ICERA (self)->priv->unsolicited_setup, + FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *self) +{ + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_zte_icera_parent_class)->setup_ports (self); + + /* Now reset the unsolicited messages we'll handle when enabled */ + mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self), + MM_BROADBAND_MODEM_ZTE_ICERA (self)->priv->unsolicited_setup, + FALSE); +} + +/*****************************************************************************/ + +MMBroadbandModemZteIcera * +mm_broadband_modem_zte_icera_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_ZTE_ICERA, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer (AT) and Icera bearer (NET) supported */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_zte_icera_init (MMBroadbandModemZteIcera *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_MODEM_ZTE_ICERA, + MMBroadbandModemZteIceraPrivate); + self->priv->unsolicited_setup = mm_common_zte_unsolicited_setup_new (); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemZteIcera *self = MM_BROADBAND_MODEM_ZTE_ICERA (object); + + mm_common_zte_unsolicited_setup_free (self->priv->unsolicited_setup); + + G_OBJECT_CLASS (mm_broadband_modem_zte_icera_parent_class)->finalize (object); +} + + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; +} + +static void +mm_broadband_modem_zte_icera_class_init (MMBroadbandModemZteIceraClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemZteIceraPrivate)); + + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/zte/mm-broadband-modem-zte-icera.h b/src/plugins/zte/mm-broadband-modem-zte-icera.h new file mode 100644 index 00000000..fb319046 --- /dev/null +++ b/src/plugins/zte/mm-broadband-modem-zte-icera.h @@ -0,0 +1,51 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_ZTE_ICERA_H +#define MM_BROADBAND_MODEM_ZTE_ICERA_H + +#include "mm-broadband-modem-icera.h" + +#define MM_TYPE_BROADBAND_MODEM_ZTE_ICERA (mm_broadband_modem_zte_icera_get_type ()) +#define MM_BROADBAND_MODEM_ZTE_ICERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_ZTE_ICERA, MMBroadbandModemZteIcera)) +#define MM_BROADBAND_MODEM_ZTE_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_ZTE_ICERA, MMBroadbandModemZteIceraClass)) +#define MM_IS_BROADBAND_MODEM_ZTE_ICERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_ZTE_ICERA)) +#define MM_IS_BROADBAND_MODEM_ZTE_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_ZTE_ICERA)) +#define MM_BROADBAND_MODEM_ZTE_ICERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_ZTE_ICERA, MMBroadbandModemZteIceraClass)) + +typedef struct _MMBroadbandModemZteIcera MMBroadbandModemZteIcera; +typedef struct _MMBroadbandModemZteIceraClass MMBroadbandModemZteIceraClass; +typedef struct _MMBroadbandModemZteIceraPrivate MMBroadbandModemZteIceraPrivate; + +struct _MMBroadbandModemZteIcera { + MMBroadbandModemIcera parent; + MMBroadbandModemZteIceraPrivate *priv; +}; + +struct _MMBroadbandModemZteIceraClass{ + MMBroadbandModemIceraClass parent; +}; + +GType mm_broadband_modem_zte_icera_get_type (void); + +MMBroadbandModemZteIcera *mm_broadband_modem_zte_icera_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_ZTE_ICERA_H */ diff --git a/src/plugins/zte/mm-broadband-modem-zte.c b/src/plugins/zte/mm-broadband-modem-zte.c new file mode 100644 index 00000000..35283531 --- /dev/null +++ b/src/plugins/zte/mm-broadband-modem-zte.c @@ -0,0 +1,763 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-common-zte.h" +#include "mm-broadband-modem-zte.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemZte, mm_broadband_modem_zte, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)); + +struct _MMBroadbandModemZtePrivate { + /* Unsolicited messaging setup */ + MMCommonZteUnsolicitedSetup *unsolicited_setup; +}; + +/*****************************************************************************/ +/* Unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_unlock_retries_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + gint pin1, puk1; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + response = mm_strip_tag (response, "+ZPINPUK:"); + if (sscanf (response, "%d,%d", &pin1, &puk1) == 2) { + MMUnlockRetries *retries; + + retries = mm_unlock_retries_new (); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1); + g_task_return_pointer (task, retries, g_object_unref); + } else { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid unlock retries response: '%s'", + response); + } + g_object_unref (task); +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+ZPINPUK=?", + 3, + FALSE, + (GAsyncReadyCallback)load_unlock_retries_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* After SIM unlock (Modem interface) */ + +typedef struct { + guint retries; +} ModemAfterSimUnlockContext; + +static gboolean +modem_after_sim_unlock_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void modem_after_sim_unlock_context_step (GTask *task); + +static gboolean +cpms_timeout_cb (GTask *task) +{ + ModemAfterSimUnlockContext *ctx; + + ctx = g_task_get_task_data (task); + ctx->retries--; + modem_after_sim_unlock_context_step (task); + return G_SOURCE_REMOVE; +} + +static void +cpms_try_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error) && + g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_BUSY)) { + /* Retry in 2 seconds */ + g_timeout_add_seconds (2, (GSourceFunc)cpms_timeout_cb, task); + g_error_free (error); + return; + } + + if (error) + g_error_free (error); + + /* Well, we're done */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_after_sim_unlock_context_step (GTask *task) +{ + MMBroadbandModemZte *self; + ModemAfterSimUnlockContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + if (ctx->retries == 0) { + /* Well... just return without error */ + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Consumed all attempts to wait for SIM not being busy"); + g_object_unref (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CPMS?", + 3, + FALSE, + (GAsyncReadyCallback)cpms_try_ready, + task); +} + +static gboolean +after_sim_unlock_wait_cb (GTask *task) +{ + /* Attempt to disable floods of "+ZUSIMR:2" unsolicited responses that + * eventually fill up the device's buffers and make it crash. Normally + * done during probing, but if the device has a PIN enabled it won't + * accept the +CPMS? during the probe and we have to do it here. + */ + modem_after_sim_unlock_context_step (task); + + return G_SOURCE_REMOVE; +} + +static void +modem_after_sim_unlock (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ModemAfterSimUnlockContext *ctx; + GTask *task; + + ctx = g_new (ModemAfterSimUnlockContext, 1); + ctx->retries = 3; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + g_timeout_add_seconds (1, (GSourceFunc)after_sim_unlock_wait_cb, task); +} + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Use AT+CFUN=4 for power down. It will stop the RF (IMSI detach), and + * keeps access to the SIM */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=4", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMModemModeCombination mode; + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + if (!mm_iface_modem_is_3gpp_lte (self)) { + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G, 2G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + /* 2G and 3G, 3G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + } else { + /* 4G only */ + mode.allowed = MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G, 3G and 4G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + const gchar *response; + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GRegex) r = NULL; + gint cm_mode = -1; + gint pref_acq = -1; + GError *match_error = NULL; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + r = g_regex_new ("\\+ZSNT:\\s*(\\d),(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &match_error)) { + if (match_error) + g_propagate_error (error, match_error); + else + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse +ZSNT response: '%s'", + response); + return FALSE; + } + + if (!mm_get_int_from_match_info (match_info, 1, &cm_mode) || + cm_mode < 0 || (cm_mode > 2 && cm_mode != 6) || + !mm_get_int_from_match_info (match_info, 3, &pref_acq) || + pref_acq < 0 || pref_acq > 2) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse the allowed mode response: '%s'", + response); + return FALSE; + } + + /* Correctly parsed! */ + if (cm_mode == 0) { + /* Both 2G, 3G and LTE allowed. For LTE modems, no 2G/3G preference supported. */ + if (pref_acq == 0 || mm_iface_modem_is_3gpp_lte (self)) { + /* Any allowed */ + *allowed = MM_MODEM_MODE_ANY; + *preferred = MM_MODEM_MODE_NONE; + } else if (pref_acq == 1) { + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_2G; + } else if (pref_acq == 2) { + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_3G; + } else + g_assert_not_reached (); + } else if (cm_mode == 1) { + /* GSM only */ + *allowed = MM_MODEM_MODE_2G; + *preferred = MM_MODEM_MODE_NONE; + } else if (cm_mode == 2) { + /* WCDMA only */ + *allowed = MM_MODEM_MODE_3G; + *preferred = MM_MODEM_MODE_NONE; + } else if (cm_mode == 6) { + /* LTE only */ + *allowed = MM_MODEM_MODE_4G; + *preferred = MM_MODEM_MODE_NONE; + } else + g_assert_not_reached (); + + return TRUE; +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+ZSNT?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +allowed_mode_update_ready (MMBroadbandModemZte *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (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); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *command; + gint cm_mode = -1; + gint pref_acq = -1; + + task = g_task_new (self, NULL, callback, user_data); + + if (allowed == MM_MODEM_MODE_2G) { + cm_mode = 1; + pref_acq = 0; + } else if (allowed == MM_MODEM_MODE_3G) { + cm_mode = 2; + pref_acq = 0; + } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G) + && !mm_iface_modem_is_3gpp_lte (self)) { /* LTE models do not support 2G|3G mode */ + cm_mode = 0; + if (preferred == MM_MODEM_MODE_2G) + pref_acq = 1; + else if (preferred == MM_MODEM_MODE_3G) + pref_acq = 2; + else /* none preferred, so AUTO */ + pref_acq = 0; + } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G) && + preferred == MM_MODEM_MODE_NONE) { + cm_mode = 0; + pref_acq = 0; + } else if (allowed == MM_MODEM_MODE_ANY && + preferred == MM_MODEM_MODE_NONE) { + cm_mode = 0; + pref_acq = 0; + } else if (allowed == MM_MODEM_MODE_4G) { + cm_mode = 6; + pref_acq = 0; + } + + if (cm_mode < 0 || pref_acq < 0) { + 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_object_unref (task); + + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("AT+ZSNT=%d,0,%d", cm_mode, pref_acq); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)allowed_mode_update_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Load access technology (Modem interface) */ + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + const gchar *response; + + /* CDMA-only devices run parent access technology checks */ + if (mm_iface_modem_is_cdma_only (self)) { + return iface_modem_parent->load_access_technologies_finish (self, + res, + access_technologies, + mask, + error); + } + + /* Otherwise process and handle +ZPAS response from 3GPP devices */ + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + /* Sample response from an MF626: + * +ZPAS: "GPRS/EDGE","CS_ONLY" + */ + response = mm_strip_tag (response, "+ZPAS:"); + *access_technologies = mm_string_to_access_tech (response); + *mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK; + return TRUE; +} + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + + /* CDMA modems don't support ZPAS and thus run parent's access technology + * loading. */ + if (mm_iface_modem_is_cdma_only (self)) { + iface_modem_parent->load_access_technologies (self, callback, user_data); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+ZPAS?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self), + MM_BROADBAND_MODEM_ZTE (self)->priv->unsolicited_setup, + TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, + task); +} + +static void +parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Our own cleanup first */ + mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self), + MM_BROADBAND_MODEM_ZTE (self)->priv->unsolicited_setup, + FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *self) +{ + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_zte_parent_class)->setup_ports (self); + + /* Now reset the unsolicited messages we'll handle when enabled */ + mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self), + MM_BROADBAND_MODEM_ZTE (self)->priv->unsolicited_setup, + FALSE); +} + +/*****************************************************************************/ + +MMBroadbandModemZte * +mm_broadband_modem_zte_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_ZTE, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + MM_BROADBAND_MODEM_INDICATORS_DISABLED, TRUE, + NULL); +} + +static void +mm_broadband_modem_zte_init (MMBroadbandModemZte *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_MODEM_ZTE, + MMBroadbandModemZtePrivate); + self->priv->unsolicited_setup = mm_common_zte_unsolicited_setup_new (); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemZte *self = MM_BROADBAND_MODEM_ZTE (object); + + mm_common_zte_unsolicited_setup_free (self->priv->unsolicited_setup); + + G_OBJECT_CLASS (mm_broadband_modem_zte_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->modem_after_sim_unlock = modem_after_sim_unlock; + iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; +} + +static void +mm_broadband_modem_zte_class_init (MMBroadbandModemZteClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemZtePrivate)); + + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/zte/mm-broadband-modem-zte.h b/src/plugins/zte/mm-broadband-modem-zte.h new file mode 100644 index 00000000..a61eead4 --- /dev/null +++ b/src/plugins/zte/mm-broadband-modem-zte.h @@ -0,0 +1,51 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_ZTE_H +#define MM_BROADBAND_MODEM_ZTE_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_ZTE (mm_broadband_modem_zte_get_type ()) +#define MM_BROADBAND_MODEM_ZTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_ZTE, MMBroadbandModemZte)) +#define MM_BROADBAND_MODEM_ZTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_ZTE, MMBroadbandModemZteClass)) +#define MM_IS_BROADBAND_MODEM_ZTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_ZTE)) +#define MM_IS_BROADBAND_MODEM_ZTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_ZTE)) +#define MM_BROADBAND_MODEM_ZTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_ZTE, MMBroadbandModemZteClass)) + +typedef struct _MMBroadbandModemZte MMBroadbandModemZte; +typedef struct _MMBroadbandModemZteClass MMBroadbandModemZteClass; +typedef struct _MMBroadbandModemZtePrivate MMBroadbandModemZtePrivate; + +struct _MMBroadbandModemZte { + MMBroadbandModem parent; + MMBroadbandModemZtePrivate *priv; +}; + +struct _MMBroadbandModemZteClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_zte_get_type (void); + +MMBroadbandModemZte *mm_broadband_modem_zte_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_ZTE_H */ diff --git a/src/plugins/zte/mm-common-zte.c b/src/plugins/zte/mm-common-zte.c new file mode 100644 index 00000000..5c992c22 --- /dev/null +++ b/src/plugins/zte/mm-common-zte.c @@ -0,0 +1,141 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include "ModemManager.h" +#include "mm-modem-helpers.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-common-zte.h" + +struct _MMCommonZteUnsolicitedSetup { + /* Regex for access-technology related notifications */ + GRegex *zpasr_regex; + + /* Requests to always ignore */ + GRegex *zusimr_regex; /* SMS related */ + GRegex *zdonr_regex; /* Unsolicited operator display */ + GRegex *zpstm_regex; /* SIM request to Build Main Menu */ + GRegex *zend_regex; /* SIM request to Rebuild Main Menu */ +}; + +MMCommonZteUnsolicitedSetup * +mm_common_zte_unsolicited_setup_new (void) +{ + MMCommonZteUnsolicitedSetup *setup; + + setup = g_new (MMCommonZteUnsolicitedSetup, 1); + + /* Prepare regular expressions to setup */ + + setup->zusimr_regex = g_regex_new ("\\r\\n\\+ZUSIMR:(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (setup->zusimr_regex != NULL); + + setup->zdonr_regex = g_regex_new ("\\r\\n\\+ZDONR: (.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (setup->zdonr_regex != NULL); + + setup->zpasr_regex = g_regex_new ("\\r\\n\\+ZPASR:\\s*(.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (setup->zpasr_regex != NULL); + + setup->zpstm_regex = g_regex_new ("\\r\\n\\+ZPSTM: (.*)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (setup->zpstm_regex != NULL); + + setup->zend_regex = g_regex_new ("\\r\\n\\+ZEND\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (setup->zend_regex != NULL); + + return setup; +} + +void +mm_common_zte_unsolicited_setup_free (MMCommonZteUnsolicitedSetup *setup) +{ + g_regex_unref (setup->zusimr_regex); + g_regex_unref (setup->zdonr_regex); + g_regex_unref (setup->zpasr_regex); + g_regex_unref (setup->zpstm_regex); + g_regex_unref (setup->zend_regex); + g_free (setup); +} + +static void +zpasr_received (MMPortSerialAt *port, + GMatchInfo *info, + MMBroadbandModem *self) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + gchar *str; + + str = g_match_info_fetch (info, 1); + if (str) { + act = mm_string_to_access_tech (str); + g_free (str); + } + + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + act, + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); +} + +void +mm_common_zte_set_unsolicited_events_handlers (MMBroadbandModem *self, + MMCommonZteUnsolicitedSetup *setup, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* Access technology related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + setup->zpasr_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)zpasr_received : NULL, + enable ? self : NULL, + NULL); + + /* Other unsolicited events to always ignore */ + if (!enable) { + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + setup->zusimr_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + setup->zdonr_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + setup->zpstm_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + setup->zend_regex, + NULL, NULL, NULL); + } + } +} diff --git a/src/plugins/zte/mm-common-zte.h b/src/plugins/zte/mm-common-zte.h new file mode 100644 index 00000000..66ee6eeb --- /dev/null +++ b/src/plugins/zte/mm-common-zte.h @@ -0,0 +1,31 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_COMMON_ZTE_H +#define MM_COMMON_ZTE_H + +#include "mm-broadband-modem.h" + +typedef struct _MMCommonZteUnsolicitedSetup MMCommonZteUnsolicitedSetup; +MMCommonZteUnsolicitedSetup *mm_common_zte_unsolicited_setup_new (void); +void mm_common_zte_unsolicited_setup_free (MMCommonZteUnsolicitedSetup *setup); + +void mm_common_zte_set_unsolicited_events_handlers (MMBroadbandModem *self, + MMCommonZteUnsolicitedSetup *setup, + gboolean enable); + +#endif /* MM_COMMON_ZTE_H */ diff --git a/src/plugins/zte/mm-plugin-zte.c b/src/plugins/zte/mm-plugin-zte.c new file mode 100644 index 00000000..a39386e6 --- /dev/null +++ b/src/plugins/zte/mm-plugin-zte.c @@ -0,0 +1,177 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-zte.h" +#include "mm-broadband-modem-zte.h" +#include "mm-broadband-modem-zte-icera.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi.h" +#endif + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#endif + +G_DEFINE_TYPE (MMPluginZte, mm_plugin_zte, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ +/* Custom commands for AT probing */ + +/* Many ZTE devices will flood the port with "Message waiting" indications + * and eventually fill up the serial buffer and crash. We need to turn off + * that indicator. See NetworkManager commits + * 1235f71b20c92cded4abd976ccc5010649aae1a0 and + * f38ad328acfdc6ce29dd1380602c546b064161ae for more details. + * + * We use this command also for checking AT support in the port. + */ +static const MMPortProbeAtCommand custom_at_probe[] = { + { "ATE0+CPMS?", 3, mm_port_probe_response_processor_is_at }, + { "ATE0+CPMS?", 3, mm_port_probe_response_processor_is_at }, + { "ATE0+CPMS?", 3, mm_port_probe_response_processor_is_at }, + { NULL } +}; + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered ZTE modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered ZTE modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + if (mm_port_probe_list_is_icera (probes)) + return MM_BASE_MODEM (mm_broadband_modem_zte_icera_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + + return MM_BASE_MODEM (mm_broadband_modem_zte_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +static gboolean +grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + MMKernelDevice *port; + MMPortType ptype; + + port = mm_port_probe_peek_port (probe); + + /* Ignore net ports on non-Icera non-QMI modems */ + ptype = mm_port_probe_get_port_type (probe); + if (ptype == MM_PORT_TYPE_NET && MM_IS_BROADBAND_MODEM_ZTE (modem)) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Ignoring net port in ZTE modem"); + return FALSE; + } + + if (mm_kernel_device_get_global_property_as_boolean (port, "ID_MM_ZTE_ICERA_DHCP")) { + mm_obj_dbg (self, "icera-based modem will use DHCP"); + g_object_set (modem, + MM_BROADBAND_MODEM_ICERA_DEFAULT_IP_METHOD, MM_BEARER_IP_METHOD_DHCP, + NULL); + } + + return mm_base_modem_grab_port (modem, + port, + ptype, + MM_PORT_SERIAL_AT_FLAG_NONE, + error); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x19d2, 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_ZTE, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_CUSTOM_AT_PROBE, custom_at_probe, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_REQUIRED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + MM_PLUGIN_ICERA_PROBE, TRUE, + NULL)); +} + +static void +mm_plugin_zte_init (MMPluginZte *self) +{ +} + +static void +mm_plugin_zte_class_init (MMPluginZteClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = grab_port; +} diff --git a/src/plugins/zte/mm-plugin-zte.h b/src/plugins/zte/mm-plugin-zte.h new file mode 100644 index 00000000..353ce86e --- /dev/null +++ b/src/plugins/zte/mm-plugin-zte.h @@ -0,0 +1,42 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_ZTE_H +#define MM_PLUGIN_ZTE_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_ZTE (mm_plugin_zte_get_type ()) +#define MM_PLUGIN_ZTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_ZTE, MMPluginZte)) +#define MM_PLUGIN_ZTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_ZTE, MMPluginZteClass)) +#define MM_IS_PLUGIN_ZTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_ZTE)) +#define MM_IS_PLUGIN_ZTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_ZTE)) +#define MM_PLUGIN_ZTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_ZTE, MMPluginZteClass)) + +typedef struct { + MMPlugin parent; +} MMPluginZte; + +typedef struct { + MMPluginClass parent; +} MMPluginZteClass; + +GType mm_plugin_zte_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_ZTE_H */ |