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/altair | |
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/altair')
-rw-r--r-- | src/plugins/altair/mm-broadband-bearer-altair-lte.c | 370 | ||||
-rw-r--r-- | src/plugins/altair/mm-broadband-bearer-altair-lte.h | 59 | ||||
-rw-r--r-- | src/plugins/altair/mm-broadband-modem-altair-lte.c | 1313 | ||||
-rw-r--r-- | src/plugins/altair/mm-broadband-modem-altair-lte.h | 53 | ||||
-rw-r--r-- | src/plugins/altair/mm-modem-helpers-altair-lte.c | 259 | ||||
-rw-r--r-- | src/plugins/altair/mm-modem-helpers-altair-lte.h | 38 | ||||
-rw-r--r-- | src/plugins/altair/mm-plugin-altair-lte.c | 101 | ||||
-rw-r--r-- | src/plugins/altair/mm-plugin-altair-lte.h | 48 | ||||
-rw-r--r-- | src/plugins/altair/tests/test-modem-helpers-altair-lte.c | 189 |
9 files changed, 2430 insertions, 0 deletions
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 (); +} |