aboutsummaryrefslogtreecommitdiff
path: root/src/mm-generic-gsm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mm-generic-gsm.c')
-rw-r--r--src/mm-generic-gsm.c2833
1 files changed, 2326 insertions, 507 deletions
diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c
index 4954ca1..08cde10 100644
--- a/src/mm-generic-gsm.c
+++ b/src/mm-generic-gsm.c
@@ -12,13 +12,15 @@
*
* Copyright (C) 2008 - 2009 Novell, Inc.
* Copyright (C) 2009 - 2010 Red Hat, Inc.
- * Copyright (C) 2009 Ericsson
+ * Copyright (C) 2009 - 2010 Ericsson
*/
+#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
+
#include "mm-generic-gsm.h"
#include "mm-modem-gsm-card.h"
#include "mm-modem-gsm-network.h"
@@ -26,8 +28,13 @@
#include "mm-modem-simple.h"
#include "mm-errors.h"
#include "mm-callback-info.h"
+#include "mm-at-serial-port.h"
+#include "mm-qcdm-serial-port.h"
#include "mm-serial-parsers.h"
#include "mm-modem-helpers.h"
+#include "mm-options.h"
+#include "mm-properties-changed-signal.h"
+#include "mm-utils.h"
static void modem_init (MMModem *modem_class);
static void modem_gsm_card_init (MMModemGsmCard *gsm_card_class);
@@ -50,39 +57,88 @@ typedef struct {
char *device;
gboolean valid;
+ gboolean pin_checked;
+ guint32 pin_check_tries;
+ guint pin_check_timeout;
+
+ MMModemGsmAllowedMode allowed_mode;
+
+ gboolean roam_allowed;
char *oper_code;
char *oper_name;
guint32 ip_method;
- gboolean unsolicited_registration;
- MMModemGsmNetworkRegStatus reg_status;
+ GPtrArray *reg_regex;
+
+ guint poll_id;
+
+ /* CREG and CGREG info */
+ gboolean creg_poll;
+ gboolean cgreg_poll;
+ /* Index 0 for CREG, index 1 for CGREG */
+ gulong lac[2];
+ gulong cell_id[2];
+ MMModemGsmAccessTech act;
+
+ /* Index 0 for CREG, index 1 for CGREG */
+ MMModemGsmNetworkRegStatus reg_status[2];
guint pending_reg_id;
MMCallbackInfo *pending_reg_info;
+ gboolean manual_reg;
+ guint signal_quality_id;
+ time_t signal_quality_timestamp;
guint32 signal_quality;
- guint32 cid;
+ gint cid;
+
+ guint32 charsets;
+ guint32 cur_charset;
- MMSerialPort *primary;
- MMSerialPort *secondary;
+ MMAtSerialPort *primary;
+ MMAtSerialPort *secondary;
+ MMQcdmSerialPort *qcdm;
MMPort *data;
} MMGenericGsmPrivate;
-static void get_registration_status (MMSerialPort *port, MMCallbackInfo *info);
-static void read_operator_code_done (MMSerialPort *port,
+static void get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info);
+static void read_operator_code_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data);
-static void read_operator_name_done (MMSerialPort *port,
+static void read_operator_name_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data);
-static void reg_state_changed (MMSerialPort *port,
+static void reg_state_changed (MMAtSerialPort *port,
GMatchInfo *match_info,
gpointer user_data);
+static void get_reg_status_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data);
+
+static gboolean handle_reg_status_response (MMGenericGsm *self,
+ GString *response,
+ GError **error);
+
+static MMModemGsmAccessTech etsi_act_to_mm_act (gint act);
+
+static void _internal_update_access_technology (MMGenericGsm *modem,
+ MMModemGsmAccessTech act);
+
+static void reg_info_updated (MMGenericGsm *self,
+ gboolean update_rs,
+ MMGenericGsmRegType rs_type,
+ MMModemGsmNetworkRegStatus status,
+ gboolean update_code,
+ const char *oper_code,
+ gboolean update_name,
+ const char *oper_name);
+
MMModem *
mm_generic_gsm_new (const char *device,
const char *driver,
@@ -99,24 +155,7 @@ mm_generic_gsm_new (const char *device,
NULL));
}
-void
-mm_generic_gsm_set_unsolicited_registration (MMGenericGsm *modem,
- gboolean enabled)
-{
- g_return_if_fail (MM_IS_GENERIC_GSM (modem));
-
- MM_GENERIC_GSM_GET_PRIVATE (modem)->unsolicited_registration = enabled;
-}
-
-void
-mm_generic_gsm_set_cid (MMGenericGsm *modem, guint32 cid)
-{
- g_return_if_fail (MM_IS_GENERIC_GSM (modem));
-
- MM_GENERIC_GSM_GET_PRIVATE (modem)->cid = cid;
-}
-
-guint32
+gint
mm_generic_gsm_get_cid (MMGenericGsm *modem)
{
g_return_val_if_fail (MM_IS_GENERIC_GSM (modem), 0);
@@ -124,93 +163,98 @@ mm_generic_gsm_get_cid (MMGenericGsm *modem)
return MM_GENERIC_GSM_GET_PRIVATE (modem)->cid;
}
-static void
-got_signal_quality (MMModem *modem,
- guint32 result,
- GError *error,
- gpointer user_data)
-{
-}
-
-void
-mm_generic_gsm_set_reg_status (MMGenericGsm *modem,
- MMModemGsmNetworkRegStatus status)
-{
- MMGenericGsmPrivate *priv;
-
- g_return_if_fail (MM_IS_GENERIC_GSM (modem));
-
- priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
-
- if (priv->reg_status != status) {
- priv->reg_status = status;
-
- g_debug ("Registration state changed: %d", status);
-
- if (status == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME ||
- status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) {
- mm_serial_port_queue_command (priv->primary, "+COPS=3,2;+COPS?", 3, read_operator_code_done, modem);
- mm_serial_port_queue_command (priv->primary, "+COPS=3,0;+COPS?", 3, read_operator_name_done, modem);
- mm_modem_gsm_network_get_signal_quality (MM_MODEM_GSM_NETWORK (modem), got_signal_quality, NULL);
- } else {
- g_free (priv->oper_code);
- g_free (priv->oper_name);
- priv->oper_code = priv->oper_name = NULL;
+typedef struct {
+ const char *result;
+ const char *normalized;
+ guint code;
+} CPinResult;
- mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (modem), priv->reg_status,
- priv->oper_code, priv->oper_name);
- }
+static CPinResult unlock_results[] = {
+ /* Longer entries first so we catch the correct one with strcmp() */
+ { "PH-NETSUB PIN", "ph-netsub-pin", MM_MOBILE_ERROR_NETWORK_SUBSET_PIN },
+ { "PH-NETSUB PUK", "ph-netsub-puk", MM_MOBILE_ERROR_NETWORK_SUBSET_PUK },
+ { "PH-FSIM PIN", "ph-fsim-pin", MM_MOBILE_ERROR_PH_FSIM_PIN },
+ { "PH-FSIM PUK", "ph-fsim-puk", MM_MOBILE_ERROR_PH_FSIM_PUK },
+ { "PH-CORP PIN", "ph-corp-pin", MM_MOBILE_ERROR_CORP_PIN },
+ { "PH-CORP PUK", "ph-corp-puk", MM_MOBILE_ERROR_CORP_PUK },
+ { "PH-SIM PIN", "ph-sim-pin", MM_MOBILE_ERROR_PH_SIM_PIN },
+ { "PH-NET PIN", "ph-net-pin", MM_MOBILE_ERROR_NETWORK_PIN },
+ { "PH-NET PUK", "ph-net-puk", MM_MOBILE_ERROR_NETWORK_PUK },
+ { "PH-SP PIN", "ph-sp-pin", MM_MOBILE_ERROR_SERVICE_PIN },
+ { "PH-SP PUK", "ph-sp-puk", MM_MOBILE_ERROR_SERVICE_PUK },
+ { "SIM PIN2", "sim-pin2", MM_MOBILE_ERROR_SIM_PIN2 },
+ { "SIM PUK2", "sim-puk2", MM_MOBILE_ERROR_SIM_PUK2 },
+ { "SIM PIN", "sim-pin", MM_MOBILE_ERROR_SIM_PIN },
+ { "SIM PUK", "sim-puk", MM_MOBILE_ERROR_SIM_PUK },
+ { NULL, NULL, MM_MOBILE_ERROR_PHONE_FAILURE },
+};
+
+static GError *
+error_for_unlock_required (const char *unlock)
+{
+ CPinResult *iter = &unlock_results[0];
+
+ if (!unlock || !strlen (unlock))
+ return NULL;
- mm_generic_gsm_update_enabled_state (modem, TRUE, MM_MODEM_STATE_REASON_NONE);
+ /* Translate the error */
+ while (iter->result) {
+ if (!strcmp (iter->normalized, unlock))
+ return mm_mobile_error_for_code (iter->code);
+ iter++;
}
+
+ return g_error_new (MM_MOBILE_ERROR,
+ MM_MOBILE_ERROR_UNKNOWN,
+ "Unknown unlock request '%s'", unlock);
}
-typedef struct {
- const char *result;
- guint code;
-} CPinResult;
+static void
+get_unlock_retries_cb (MMModem *modem,
+ guint32 result,
+ GError *error,
+ gpointer user_data)
+{
+ if (!error)
+ mm_modem_base_set_unlock_retries (MM_MODEM_BASE (modem), result);
+ else
+ mm_modem_base_set_unlock_retries (MM_MODEM_BASE (modem), MM_MODEM_GSM_CARD_UNLOCK_RETRIES_NOT_SUPPORTED);
+}
static void
-pin_check_done (MMSerialPort *port,
+pin_check_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
gboolean parsed = FALSE;
- static CPinResult results[] = {
- { "SIM PIN", MM_MOBILE_ERROR_SIM_PIN },
- { "SIM PUK", MM_MOBILE_ERROR_SIM_PUK },
- { "PH-SIM PIN", MM_MOBILE_ERROR_PH_SIM_PIN },
- { "PH-FSIM PIN", MM_MOBILE_ERROR_PH_FSIM_PIN },
- { "PH-FSIM PUK", MM_MOBILE_ERROR_PH_FSIM_PUK },
- { "SIM PIN2", MM_MOBILE_ERROR_SIM_PIN2 },
- { "SIM PUK2", MM_MOBILE_ERROR_SIM_PUK2 },
- { "PH-NET PIN", MM_MOBILE_ERROR_NETWORK_PIN },
- { "PH-NET PUK", MM_MOBILE_ERROR_NETWORK_PUK },
- { "PH-NETSUB PIN", MM_MOBILE_ERROR_NETWORK_SUBSET_PIN },
- { "PH-NETSUB PUK", MM_MOBILE_ERROR_NETWORK_SUBSET_PUK },
- { "PH-SP PIN", MM_MOBILE_ERROR_SERVICE_PIN },
- { "PH-SP PUK", MM_MOBILE_ERROR_SERVICE_PUK },
- { "PH-CORP PIN", MM_MOBILE_ERROR_CORP_PIN },
- { "PH-CORP PUK", MM_MOBILE_ERROR_CORP_PUK },
- { NULL, MM_MOBILE_ERROR_PHONE_FAILURE },
- };
if (error)
info->error = g_error_copy (error);
- else if (g_str_has_prefix (response->str, "+CPIN: ")) {
- const char *str = response->str + 7;
+ else if (response && strstr (response->str, "+CPIN: ")) {
+ const char *str = strstr (response->str, "+CPIN: ") + 7;
- if (g_str_has_prefix (str, "READY"))
+ if (g_str_has_prefix (str, "READY")) {
+ mm_modem_base_set_unlock_required (MM_MODEM_BASE (info->modem), NULL);
+ if (MM_MODEM_GSM_CARD_GET_INTERFACE (info->modem)->get_unlock_retries)
+ mm_modem_base_set_unlock_retries (MM_MODEM_BASE (info->modem), 0);
+ else
+ mm_modem_base_set_unlock_retries (MM_MODEM_BASE (info->modem),
+ MM_MODEM_GSM_CARD_UNLOCK_RETRIES_NOT_SUPPORTED);
parsed = TRUE;
- else {
- CPinResult *iter = &results[0];
+ } else {
+ CPinResult *iter = &unlock_results[0];
/* Translate the error */
while (iter->result) {
if (g_str_has_prefix (str, iter->result)) {
info->error = mm_mobile_error_for_code (iter->code);
+ mm_modem_base_set_unlock_required (MM_MODEM_BASE (info->modem), iter->normalized);
+ mm_modem_gsm_card_get_unlock_retries (MM_MODEM_GSM_CARD (info->modem),
+ iter->normalized,
+ get_unlock_retries_cb,
+ NULL);
parsed = TRUE;
break;
}
@@ -219,20 +263,26 @@ pin_check_done (MMSerialPort *port,
}
}
- if (!info->error && !parsed) {
- info->error = g_error_new (MM_MODEM_ERROR,
- MM_MODEM_ERROR_GENERAL,
- "Could not parse PIN request response '%s'",
- response->str);
+ if (!parsed) {
+ /* Assume unlocked if we don't recognize the pin request result */
+ mm_modem_base_set_unlock_required (MM_MODEM_BASE (info->modem), NULL);
+ mm_modem_base_set_unlock_retries (MM_MODEM_BASE (info->modem), 0);
+
+ if (!info->error) {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Could not parse PIN request response '%s'",
+ response->str);
+ }
}
mm_callback_info_schedule (info);
}
-void
-mm_generic_gsm_check_pin (MMGenericGsm *modem,
- MMModemFn callback,
- gpointer user_data)
+static void
+check_pin (MMGenericGsm *modem,
+ MMModemFn callback,
+ gpointer user_data)
{
MMGenericGsmPrivate *priv;
MMCallbackInfo *info;
@@ -241,25 +291,66 @@ mm_generic_gsm_check_pin (MMGenericGsm *modem,
priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
- mm_serial_port_queue_command (priv->primary, "+CPIN?", 3, pin_check_done, info);
+ mm_at_serial_port_queue_command (priv->primary, "+CPIN?", 3, pin_check_done, info);
+}
+
+static void
+get_imei_cb (MMModem *modem,
+ const char *result,
+ GError *error,
+ gpointer user_data)
+{
+ if (modem) {
+ mm_modem_base_set_equipment_identifier (MM_MODEM_BASE (modem), error ? "" : result);
+ mm_serial_port_close (MM_SERIAL_PORT (MM_GENERIC_GSM_GET_PRIVATE (modem)->primary));
+ }
}
/*****************************************************************************/
+static MMModemGsmNetworkRegStatus
+gsm_reg_status (MMGenericGsm *self)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ /* Some devices (Blackberries for example) will respond to +CGREG, but
+ * return ERROR for +CREG, probably because their firmware is just stupid.
+ * So here we prefer the +CREG response, but if we never got a successful
+ * +CREG response, we'll take +CGREG instead.
+ */
+
+ if ( priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME
+ || priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING)
+ return priv->reg_status[0];
+
+ if ( priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME
+ || priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING)
+ return priv->reg_status[1];
+
+ if (priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING)
+ return priv->reg_status[0];
+
+ if (priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING)
+ return priv->reg_status[1];
+
+ if (priv->reg_status[0] != MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN)
+ return priv->reg_status[0];
+
+ return priv->reg_status[1];
+}
+
void
mm_generic_gsm_update_enabled_state (MMGenericGsm *self,
gboolean stay_connected,
MMModemStateReason reason)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
-
/* While connected we don't want registration status changes to change
* the modem's state away from CONNECTED.
*/
if (stay_connected && (mm_modem_get_state (MM_MODEM (self)) >= MM_MODEM_STATE_DISCONNECTING))
return;
- switch (priv->reg_status) {
+ switch (gsm_reg_status (self)) {
case MM_MODEM_GSM_NETWORK_REG_STATUS_HOME:
case MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING:
mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_REGISTERED, reason);
@@ -282,12 +373,109 @@ check_valid (MMGenericGsm *self)
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
gboolean new_valid = FALSE;
- if (priv->primary && priv->data)
+ if (priv->primary && priv->data && priv->pin_checked)
new_valid = TRUE;
mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid);
}
+
+static void initial_pin_check_done (MMModem *modem, GError *error, gpointer user_data);
+
+static gboolean
+pin_check_again (gpointer user_data)
+{
+ MMGenericGsm *self = MM_GENERIC_GSM (user_data);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ priv->pin_check_timeout = 0;
+ check_pin (self, initial_pin_check_done, NULL);
+ return FALSE;
+}
+
+static void
+initial_pin_check_done (MMModem *modem, GError *error, gpointer user_data)
+{
+ MMGenericGsmPrivate *priv;
+
+ /* modem could have been removed before we get here, in which case
+ * 'modem' will be NULL.
+ */
+ if (!modem)
+ return;
+
+ g_return_if_fail (MM_IS_GENERIC_GSM (modem));
+ priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+
+ if ( error
+ && priv->pin_check_tries++ < 3
+ && !mm_modem_base_get_unlock_required (MM_MODEM_BASE (modem))) {
+ /* Try it again a few times */
+ if (priv->pin_check_timeout)
+ g_source_remove (priv->pin_check_timeout);
+ priv->pin_check_timeout = g_timeout_add_seconds (2, pin_check_again, modem);
+ } else {
+ priv->pin_checked = TRUE;
+ mm_serial_port_close (MM_SERIAL_PORT (priv->primary));
+ check_valid (MM_GENERIC_GSM (modem));
+ }
+}
+
+static void
+initial_pin_check (MMGenericGsm *self)
+{
+ GError *error = NULL;
+ MMGenericGsmPrivate *priv;
+
+ g_return_if_fail (MM_IS_GENERIC_GSM (self));
+ priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ g_return_if_fail (priv->primary != NULL);
+
+ if (mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error))
+ check_pin (self, initial_pin_check_done, NULL);
+ else {
+ g_warning ("%s: failed to open serial port: (%d) %s",
+ __func__,
+ error ? error->code : -1,
+ error && error->message ? error->message : "(unknown)");
+ g_clear_error (&error);
+
+ /* Ensure the modem is still somewhat usable if opening the serial
+ * port fails for some reason.
+ */
+ initial_pin_check_done (MM_MODEM (self), NULL, NULL);
+ }
+}
+
+static void
+initial_imei_check (MMGenericGsm *self)
+{
+ GError *error = NULL;
+ MMGenericGsmPrivate *priv;
+
+ g_return_if_fail (MM_IS_GENERIC_GSM (self));
+ priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ g_return_if_fail (priv->primary != NULL);
+
+ if (mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) {
+ /* Make sure echoing is off */
+ mm_at_serial_port_queue_command (priv->primary, "E0", 3, NULL, NULL);
+
+ /* Get modem's imei number */
+ mm_modem_gsm_card_get_imei (MM_MODEM_GSM_CARD (self),
+ get_imei_cb,
+ NULL);
+ } else {
+ g_warning ("%s: failed to open serial port: (%d) %s",
+ __func__,
+ error ? error->code : -1,
+ error && error->message ? error->message : "(unknown)");
+ g_clear_error (&error);
+ }
+}
+
static gboolean
owns_port (MMModem *modem, const char *subsys, const char *name)
{
@@ -308,28 +496,50 @@ mm_generic_gsm_grab_port (MMGenericGsm *self,
g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), FALSE);
port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype);
- if (port && MM_IS_SERIAL_PORT (port)) {
- mm_serial_port_set_response_parser (MM_SERIAL_PORT (port),
- mm_serial_parser_v1_parse,
- mm_serial_parser_v1_new (),
- mm_serial_parser_v1_destroy);
+ if (!port) {
+ g_warn_if_fail (port != NULL);
+ return NULL;
+ }
+
+ if (MM_IS_AT_SERIAL_PORT (port)) {
+ GPtrArray *array;
+ int i;
+
+ mm_at_serial_port_set_response_parser (MM_AT_SERIAL_PORT (port),
+ mm_serial_parser_v1_parse,
+ mm_serial_parser_v1_new (),
+ mm_serial_parser_v1_destroy);
- regex = g_regex_new ("\\r\\n\\+CREG: (\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
- mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, reg_state_changed, self, NULL);
- g_regex_unref (regex);
+ /* Set up CREG unsolicited message handlers */
+ array = mm_gsm_creg_regex_get (FALSE);
+ for (i = 0; i < array->len; i++) {
+ regex = g_ptr_array_index (array, i);
+
+ mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, reg_state_changed, self, NULL);
+ }
+ mm_gsm_creg_regex_destroy (array);
if (ptype == MM_PORT_TYPE_PRIMARY) {
- priv->primary = MM_SERIAL_PORT (port);
+ priv->primary = MM_AT_SERIAL_PORT (port);
if (!priv->data) {
priv->data = port;
g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE);
}
- check_valid (self);
+
+ /* Get modem's initial lock/unlock state */
+ initial_pin_check (self);
+
+ /* Get modem's IMEI number */
+ initial_imei_check (self);
+
} else if (ptype == MM_PORT_TYPE_SECONDARY)
- priv->secondary = MM_SERIAL_PORT (port);
- } else {
+ priv->secondary = MM_AT_SERIAL_PORT (port);
+ } else if (MM_IS_QCDM_SERIAL_PORT (port)) {
+ if (!priv->qcdm)
+ priv->qcdm = MM_QCDM_SERIAL_PORT (port);
+ } else if (!strcmp (subsys, "net")) {
/* Net device (if any) is the preferred data port */
- if (!priv->data || MM_IS_SERIAL_PORT (priv->data)) {
+ if (!priv->data || MM_IS_AT_SERIAL_PORT (priv->data)) {
priv->data = port;
g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE);
check_valid (self);
@@ -381,7 +591,7 @@ release_port (MMModem *modem, const char *subsys, const char *name)
if (!port)
return;
- if (port == MM_PORT (priv->primary)) {
+ if (port == (MMPort *) priv->primary) {
mm_modem_base_remove_port (MM_MODEM_BASE (modem), port);
priv->primary = NULL;
}
@@ -391,59 +601,375 @@ release_port (MMModem *modem, const char *subsys, const char *name)
g_object_notify (G_OBJECT (modem), MM_MODEM_DATA_DEVICE);
}
- if (port == MM_PORT (priv->secondary)) {
+ if (port == (MMPort *) priv->secondary) {
mm_modem_base_remove_port (MM_MODEM_BASE (modem), port);
priv->secondary = NULL;
}
+ if (port == (MMPort *) priv->qcdm) {
+ mm_modem_base_remove_port (MM_MODEM_BASE (modem), port);
+ priv->qcdm = NULL;
+ }
+
check_valid (MM_GENERIC_GSM (modem));
}
+static void
+reg_poll_response (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMGenericGsm *self = MM_GENERIC_GSM (user_data);
+
+ if (!error)
+ handle_reg_status_response (self, response, NULL);
+}
+
+static void
+periodic_signal_quality_cb (MMModem *modem,
+ guint32 result,
+ GError *error,
+ gpointer user_data)
+{
+ /* Cached signal quality already updated */
+}
+
+static void
+periodic_access_tech_cb (MMModem *modem,
+ guint32 act,
+ GError *error,
+ gpointer user_data)
+{
+ if (modem && !error && act)
+ mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (modem), act);
+}
+
+static gboolean
+periodic_poll_cb (gpointer user_data)
+{
+ MMGenericGsm *self = MM_GENERIC_GSM (user_data);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ MMAtSerialPort *port;
+
+ port = mm_generic_gsm_get_best_at_port (self, NULL);
+ if (!port)
+ return TRUE; /* oh well, try later */
+
+ if (priv->creg_poll)
+ mm_at_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, self);
+ if (priv->cgreg_poll)
+ mm_at_serial_port_queue_command (port, "+CGREG?", 10, reg_poll_response, self);
+
+ mm_modem_gsm_network_get_signal_quality (MM_MODEM_GSM_NETWORK (self),
+ periodic_signal_quality_cb,
+ NULL);
+
+ if (MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology)
+ MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology (self, periodic_access_tech_cb, NULL);
+
+ return TRUE; /* continue running */
+}
+
+static void
+cgreg1_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->modem) {
+ if (info->error) {
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ g_clear_error (&info->error);
+
+ /* The modem doesn't like unsolicited CGREG, so we'll need to poll */
+ priv->cgreg_poll = TRUE;
+ }
+ /* Success; get initial state */
+ mm_at_serial_port_queue_command (port, "+CGREG?", 10, reg_poll_response, info->modem);
+ }
+ mm_callback_info_schedule (info);
+}
+
+static void
+cgreg2_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+
+ /* Ignore errors except modem removal errors */
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->modem) {
+ if (info->error) {
+ g_clear_error (&info->error);
+ /* Try CGREG=1 instead */
+ mm_at_serial_port_queue_command (port, "+CGREG=1", 3, cgreg1_done, info);
+ } else {
+ /* Success; get initial state */
+ mm_at_serial_port_queue_command (port, "+CGREG?", 10, reg_poll_response, info->modem);
+
+ /* All done */
+ mm_callback_info_schedule (info);
+ }
+ } else {
+ /* Modem got removed */
+ mm_callback_info_schedule (info);
+ }
+}
+
+static void
+creg1_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->modem) {
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ if (info->error) {
+ g_clear_error (&info->error);
+
+ /* The modem doesn't like unsolicited CREG, so we'll need to poll */
+ priv->creg_poll = TRUE;
+ }
+ /* Success; get initial state */
+ mm_at_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, info->modem);
+
+ /* Now try to set up CGREG messages */
+ mm_at_serial_port_queue_command (port, "+CGREG=2", 3, cgreg2_done, info);
+ } else {
+ /* Modem got removed */
+ mm_callback_info_schedule (info);
+ }
+}
+
+static void
+creg2_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+
+ /* Ignore errors except modem removal errors */
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->modem) {
+ if (info->error) {
+ g_clear_error (&info->error);
+ mm_at_serial_port_queue_command (port, "+CREG=1", 3, creg1_done, info);
+ } else {
+ /* Success; get initial state */
+ mm_at_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, info->modem);
+
+ /* Now try to set up CGREG messages */
+ mm_at_serial_port_queue_command (port, "+CGREG=2", 3, cgreg2_done, info);
+ }
+ } else {
+ /* Modem got removed */
+ mm_callback_info_schedule (info);
+ }
+}
+
+static void
+enable_failed (MMModem *modem, GError *error, MMCallbackInfo *info)
+{
+ MMGenericGsmPrivate *priv;
+
+ info->error = mm_modem_check_removed (modem, error);
+
+ if (modem) {
+ mm_modem_set_state (modem,
+ MM_MODEM_STATE_DISABLED,
+ MM_MODEM_STATE_REASON_NONE);
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+
+ if (priv->primary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->primary)))
+ mm_serial_port_close_force (MM_SERIAL_PORT (priv->primary));
+ if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary)))
+ mm_serial_port_close_force (MM_SERIAL_PORT (priv->secondary));
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static guint32 best_charsets[] = {
+ MM_MODEM_CHARSET_UTF8,
+ MM_MODEM_CHARSET_UCS2,
+ MM_MODEM_CHARSET_8859_1,
+ MM_MODEM_CHARSET_IRA,
+ MM_MODEM_CHARSET_UNKNOWN
+};
+
+static void
+enabled_set_charset_done (MMModem *modem,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ guint idx;
+
+ /* only modem removals are really a hard error */
+ if (error) {
+ if (!modem) {
+ enable_failed (modem, error, info);
+ return;
+ }
+
+ /* Try the next best charset */
+ idx = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "best-charset")) + 1;
+ if (best_charsets[idx] == MM_MODEM_CHARSET_UNKNOWN) {
+ GError *tmp_error;
+
+ /* No more character sets we can use */
+ tmp_error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_UNSUPPORTED_CHARSET,
+ "Failed to find a usable modem character set");
+ enable_failed (modem, tmp_error, info);
+ g_error_free (tmp_error);
+ } else {
+ /* Send the new charset */
+ mm_callback_info_set_data (info, "best-charset", GUINT_TO_POINTER (idx), NULL);
+ mm_modem_set_charset (modem, best_charsets[idx], enabled_set_charset_done, info);
+ }
+ } else {
+ /* Modem is now enabled; update the state */
+ mm_generic_gsm_update_enabled_state (MM_GENERIC_GSM (modem), FALSE, MM_MODEM_STATE_REASON_NONE);
+
+ /* Set up unsolicited registration notifications */
+ mm_at_serial_port_queue_command (MM_GENERIC_GSM_GET_PRIVATE (modem)->primary,
+ "+CREG=2", 3, creg2_done, info);
+ }
+}
+
+static void
+supported_charsets_done (MMModem *modem,
+ guint32 charsets,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ if (!modem) {
+ enable_failed (modem, error, info);
+ return;
+ }
+
+ /* Switch the device's charset; we prefer UTF-8, but UCS2 will do too */
+ mm_modem_set_charset (modem, MM_MODEM_CHARSET_UTF8, enabled_set_charset_done, info);
+}
+
+static void
+get_allowed_mode_done (MMModem *modem,
+ MMModemGsmAllowedMode mode,
+ GError *error,
+ gpointer user_data)
+{
+ if (modem) {
+ mm_generic_gsm_update_allowed_mode (MM_GENERIC_GSM (modem),
+ error ? MM_MODEM_GSM_ALLOWED_MODE_ANY : mode);
+ }
+}
+
+static void
+get_enable_info_done (MMModem *modem,
+ const char *manufacturer,
+ const char *model,
+ const char *version,
+ GError *error,
+ gpointer user_data)
+{
+ /* Modem base class handles the response for us */
+}
+
void
-mm_generic_gsm_enable_complete (MMGenericGsm *modem,
+mm_generic_gsm_enable_complete (MMGenericGsm *self,
GError *error,
MMCallbackInfo *info)
{
- g_return_if_fail (modem != NULL);
- g_return_if_fail (MM_IS_GENERIC_GSM (modem));
+ MMGenericGsmPrivate *priv;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (MM_IS_GENERIC_GSM (self));
g_return_if_fail (info != NULL);
+ priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
if (error) {
- mm_modem_set_state (MM_MODEM (modem),
- MM_MODEM_STATE_DISABLED,
- MM_MODEM_STATE_REASON_NONE);
+ enable_failed ((MMModem *) self, error, info);
+ return;
+ }
- info->error = g_error_copy (error);
- } else {
- /* Modem is enabled; update the state */
- mm_generic_gsm_update_enabled_state (modem, FALSE, MM_MODEM_STATE_REASON_NONE);
+ /* Open the second port here if the modem has one. We'll use it for
+ * signal strength and registration updates when the device is connected,
+ * but also many devices will send unsolicited registration or other
+ * messages to the secondary port but not the primary.
+ */
+ if (priv->secondary) {
+ if (!mm_serial_port_open (MM_SERIAL_PORT (priv->secondary), &error)) {
+ if (mm_options_debug ()) {
+ g_warning ("%s: error opening secondary port: (%d) %s",
+ __func__,
+ error ? error->code : -1,
+ error && error->message ? error->message : "(unknown)");
+ }
+ }
}
- mm_callback_info_schedule (info);
+ /* Try to enable XON/XOFF flow control */
+ mm_at_serial_port_queue_command (priv->primary, "+IFC=1,1", 3, NULL, NULL);
+
+ /* Grab device info right away */
+ mm_modem_get_info (MM_MODEM (self), get_enable_info_done, NULL);
+
+ /* Get allowed mode */
+ if (MM_GENERIC_GSM_GET_CLASS (self)->get_allowed_mode)
+ MM_GENERIC_GSM_GET_CLASS (self)->get_allowed_mode (self, get_allowed_mode_done, NULL);
+
+ /* And supported character sets */
+ mm_modem_get_supported_charsets (MM_MODEM (self), supported_charsets_done, info);
}
static void
-enable_done (MMSerialPort *port,
+real_do_enable_power_up_done (MMGenericGsm *self,
+ GString *response,
+ GError *error,
+ MMCallbackInfo *info)
+{
+ /* Ignore power-up errors as not all devices actually support CFUN=1 */
+ mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), NULL, info);
+}
+
+static void
+enable_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- /* Ignore power-up command errors, not all devices actually support
- * CFUN=1.
- */
- /* FIXME: instead of just ignoring errors, since we allow subclasses
- * to override the power-on command, make a class function for powering
- * on the phone and let the subclass decided whether it wants to handle
- * errors or ignore them.
+ /* Let subclasses handle the power up command response/error; many devices
+ * don't support +CFUN, but for those that do let them handle the error
+ * correctly.
*/
-
- mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), NULL, info);
+ g_assert (MM_GENERIC_GSM_GET_CLASS (info->modem)->do_enable_power_up_done);
+ MM_GENERIC_GSM_GET_CLASS (info->modem)->do_enable_power_up_done (MM_GENERIC_GSM (info->modem),
+ response,
+ error,
+ info);
}
static void
-init_done (MMSerialPort *port,
+init_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -457,22 +983,22 @@ init_done (MMSerialPort *port,
}
/* Ensure echo is off after the init command; some modems ignore the
- * E0 when it's in the same like as ATZ (Option GIO322).
- */
- mm_serial_port_queue_command (port, "E0 +CMEE=1", 2, NULL, NULL);
+ * E0 when it's in the same line as ATZ (Option GIO322).
+ */
+ mm_at_serial_port_queue_command (port, "E0", 2, NULL, NULL);
+
+ /* Some phones (like Blackberries) don't support +CMEE=1, so make it
+ * optional. It completely violates 3GPP TS 27.007 (9.1) but what can we do...
+ */
+ mm_at_serial_port_queue_command (port, "+CMEE=1", 2, NULL, NULL);
g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD_OPTIONAL, &cmd, NULL);
- mm_serial_port_queue_command (port, cmd, 2, NULL, NULL);
+ mm_at_serial_port_queue_command (port, cmd, 2, NULL, NULL);
g_free (cmd);
- if (MM_GENERIC_GSM_GET_PRIVATE (info->modem)->unsolicited_registration)
- mm_serial_port_queue_command (port, "+CREG=1", 5, NULL, NULL);
- else
- mm_serial_port_queue_command (port, "+CREG=0", 5, NULL, NULL);
-
g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_UP_CMD, &cmd, NULL);
if (cmd && strlen (cmd))
- mm_serial_port_queue_command (port, cmd, 5, enable_done, user_data);
+ mm_at_serial_port_queue_command (port, cmd, 5, enable_done, user_data);
else
enable_done (port, NULL, NULL, user_data);
g_free (cmd);
@@ -490,7 +1016,7 @@ enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
}
g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD, &cmd, NULL);
- mm_serial_port_queue_command (port, cmd, 3, init_done, user_data);
+ mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), cmd, 3, init_done, user_data);
g_free (cmd);
}
@@ -501,7 +1027,7 @@ real_do_enable (MMGenericGsm *self, MMModemFn callback, gpointer user_data)
MMCallbackInfo *info;
info = mm_callback_info_new (MM_MODEM (self), callback, user_data);
- mm_serial_port_flash (priv->primary, 100, enable_flash_done, info);
+ mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 100, FALSE, enable_flash_done, info);
}
static void
@@ -511,11 +1037,25 @@ enable (MMModem *modem,
{
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
GError *error = NULL;
+ const char *unlock;
+
+ /* If the device needs a PIN, deal with that now, but we don't care
+ * about SIM-PIN2/SIM-PUK2 since the device is operational without it.
+ */
+ unlock = mm_modem_base_get_unlock_required (MM_MODEM_BASE (modem));
+ if (unlock && strcmp (unlock, "sim-puk2") && strcmp (unlock, "sim-pin2")) {
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new (modem, callback, user_data);
+ info->error = error_for_unlock_required (unlock);
+ mm_callback_info_schedule (info);
+ return;
+ }
/* First, reset the previously used CID */
- mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0);
+ priv->cid = -1;
- if (!mm_serial_port_open (priv->primary, &error)) {
+ if (!mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) {
MMCallbackInfo *info;
g_assert (error);
@@ -532,7 +1072,7 @@ enable (MMModem *modem,
}
static void
-disable_done (MMSerialPort *port,
+disable_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -541,13 +1081,25 @@ disable_done (MMSerialPort *port,
info->error = mm_modem_check_removed (info->modem, error);
if (!info->error) {
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+ MMGenericGsm *self = MM_GENERIC_GSM (info->modem);
- mm_serial_port_close (port);
+ mm_serial_port_close_force (MM_SERIAL_PORT (port));
mm_modem_set_state (MM_MODEM (info->modem),
MM_MODEM_STATE_DISABLED,
MM_MODEM_STATE_REASON_NONE);
- priv->reg_status = MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN;
+
+ /* Clear out circuit-switched registration info... */
+ reg_info_updated (self,
+ MM_GENERIC_GSM_REG_TYPE_CS,
+ TRUE, MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN,
+ TRUE, NULL,
+ TRUE, NULL);
+ /* ...and packet-switched registration info */
+ reg_info_updated (self,
+ MM_GENERIC_GSM_REG_TYPE_PS,
+ TRUE, MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN,
+ TRUE, NULL,
+ TRUE, NULL);
}
mm_callback_info_schedule (info);
}
@@ -575,11 +1127,15 @@ disable_flash_done (MMSerialPort *port,
return;
}
+ /* Disable unsolicited messages */
+ mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "AT+CREG=0", 3, NULL, NULL);
+ mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "AT+CGREG=0", 3, NULL, NULL);
+
g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_DOWN_CMD, &cmd, NULL);
if (cmd && strlen (cmd))
- mm_serial_port_queue_command (port, cmd, 5, disable_done, user_data);
+ mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), cmd, 5, disable_done, user_data);
else
- disable_done (port, NULL, NULL, user_data);
+ disable_done (MM_AT_SERIAL_PORT (port), NULL, NULL, user_data);
g_free (cmd);
}
@@ -588,14 +1144,42 @@ disable (MMModem *modem,
MMModemFn callback,
gpointer user_data)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMGenericGsm *self = MM_GENERIC_GSM (modem);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
MMCallbackInfo *info;
MMModemState state;
/* First, reset the previously used CID and clean up registration */
- mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0);
+ g_warn_if_fail (priv->cid == -1);
+ priv->cid = -1;
+
mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem));
+ if (priv->poll_id) {
+ g_source_remove (priv->poll_id);
+ priv->poll_id = 0;
+ }
+
+ if (priv->signal_quality_id) {
+ g_source_remove (priv->signal_quality_id);
+ priv->signal_quality_id = 0;
+ }
+
+ if (priv->pin_check_timeout) {
+ g_source_remove (priv->pin_check_timeout);
+ priv->pin_check_timeout = 0;
+ }
+
+ priv->lac[0] = 0;
+ priv->lac[1] = 0;
+ priv->cell_id[0] = 0;
+ priv->cell_id[1] = 0;
+ _internal_update_access_technology (self, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN);
+
+ /* Close the secondary port if its open */
+ if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary)))
+ mm_serial_port_close_force (MM_SERIAL_PORT (priv->secondary));
+
info = mm_callback_info_new (modem, callback, user_data);
/* Cache the previous state so we can reset it if the operation fails */
@@ -610,13 +1194,13 @@ disable (MMModem *modem,
MM_MODEM_STATE_REASON_NONE);
if (mm_port_get_connected (MM_PORT (priv->primary)))
- mm_serial_port_flash (priv->primary, 1000, disable_flash_done, info);
+ mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disable_flash_done, info);
else
- disable_flash_done (priv->primary, NULL, info);
+ disable_flash_done (MM_SERIAL_PORT (priv->primary), NULL, info);
}
static void
-get_string_done (MMSerialPort *port,
+get_string_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -632,6 +1216,110 @@ get_string_done (MMSerialPort *port,
}
static void
+get_mnc_length_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ int sw1, sw2;
+ const char *imsi;
+ gboolean success = FALSE;
+ char hex[51];
+ char *bin;
+
+ if (error) {
+ info->error = g_error_copy (error);
+ goto done;
+ }
+
+ memset (hex, 0, sizeof (hex));
+ if (sscanf (response->str, "+CRSM:%d,%d,\"%50c\"", &sw1, &sw2, (char *) &hex) == 3)
+ success = TRUE;
+ else {
+ /* May not include quotes... */
+ if (sscanf (response->str, "+CRSM:%d,%d,%50c", &sw1, &sw2, (char *) &hex) == 3)
+ success = TRUE;
+ }
+
+ if (!success) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Could not parse the CRSM response");
+ goto done;
+ }
+
+ if ((sw1 == 0x90 && sw2 == 0x00) || (sw1 == 0x91) || (sw1 == 0x92) || (sw1 == 0x9f)) {
+ gsize buflen = 0;
+ guint32 mnc_len;
+
+ /* Make sure the buffer is only hex characters */
+ while (buflen < sizeof (hex) && hex[buflen]) {
+ if (!isxdigit (hex[buflen])) {
+ hex[buflen] = 0x0;
+ break;
+ }
+ buflen++;
+ }
+
+ /* Convert hex string to binary */
+ bin = utils_hexstr2bin (hex, &buflen);
+ if (!bin || buflen < 4) {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "SIM returned malformed response '%s'",
+ hex);
+ goto done;
+ }
+
+ /* MNC length is byte 4 of this SIM file */
+ mnc_len = bin[3] & 0xFF;
+ if (mnc_len == 2 || mnc_len == 3) {
+ imsi = mm_callback_info_get_data (info, "imsi");
+ mm_callback_info_set_result (info, g_strndup (imsi, 3 + mnc_len), g_free);
+ } else {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "SIM returned invalid MNC length %d (should be either 2 or 3)",
+ mnc_len);
+ }
+ } else {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "SIM failed to handle CRSM request (sw1 %d sw2 %d)",
+ sw1, sw2);
+ }
+
+done:
+ mm_callback_info_schedule (info);
+}
+
+static void
+get_operator_id_imsi_done (MMModem *modem,
+ const char *result,
+ GError *error,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ if (error) {
+ info->error = g_error_copy (error);
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ mm_callback_info_set_data (info, "imsi", g_strdup (result), g_free);
+
+ /* READ BINARY of EFad (Administrative Data) ETSI 51.011 section 10.3.18 */
+ mm_at_serial_port_queue_command_cached (priv->primary,
+ "+CRSM=176,28589,0,0,4",
+ 3,
+ get_mnc_length_done,
+ info);
+}
+
+static void
get_imei (MMModemGsmCard *modem,
MMModemStringFn callback,
gpointer user_data)
@@ -640,7 +1328,7 @@ get_imei (MMModemGsmCard *modem,
MMCallbackInfo *info;
info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
- mm_serial_port_queue_command_cached (priv->primary, "+CGSN", 3, get_string_done, info);
+ mm_at_serial_port_queue_command_cached (priv->primary, "+CGSN", 3, get_string_done, info);
}
static void
@@ -652,112 +1340,116 @@ get_imsi (MMModemGsmCard *modem,
MMCallbackInfo *info;
info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
- mm_serial_port_queue_command_cached (priv->primary, "+CIMI", 3, get_string_done, info);
+ mm_at_serial_port_queue_command_cached (priv->primary, "+CIMI", 3, get_string_done, info);
}
static void
-card_info_invoke (MMCallbackInfo *info)
+get_operator_id (MMModemGsmCard *modem,
+ MMModemStringFn callback,
+ gpointer user_data)
{
- MMModemInfoFn callback = (MMModemInfoFn) info->callback;
-
- callback (info->modem,
- (char *) mm_callback_info_get_data (info, "card-info-manufacturer"),
- (char *) mm_callback_info_get_data (info, "card-info-model"),
- (char *) mm_callback_info_get_data (info, "card-info-version"),
- info->error, info->user_data);
-}
-
-#define GMI_RESP_TAG "+CGMI:"
-#define GMM_RESP_TAG "+CGMM:"
-#define GMR_RESP_TAG "+CGMR:"
+ MMCallbackInfo *info;
-static const char *
-strip_tag (const char *str, const char *tag)
-{
- /* Strip the response header, if any */
- if (strncmp (str, tag, strlen (tag)) == 0)
- str += strlen (tag);
- while (*str && isspace (*str))
- str++;
- return str;
+ info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
+ mm_modem_gsm_card_get_imsi (MM_MODEM_GSM_CARD (modem),
+ get_operator_id_imsi_done,
+ info);
}
static void
-get_version_done (MMSerialPort *port,
- GString *response,
- GError *error,
- gpointer user_data)
+get_card_info (MMModem *modem,
+ MMModemInfoFn callback,
+ gpointer user_data)
{
- MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- const char *resp = strip_tag (response->str, GMR_RESP_TAG);
-
- if (!error)
- mm_callback_info_set_data (info, "card-info-version", g_strdup (resp), g_free);
- else if (!info->error)
- info->error = g_error_copy (error);
+ MMAtSerialPort *port;
+ GError *error = NULL;
- mm_callback_info_schedule (info);
+ port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &error);
+ mm_modem_base_get_card_info (MM_MODEM_BASE (modem), port, error, callback, user_data);
+ g_clear_error (&error);
}
+#define PIN_PORT_TAG "pin-port"
+
static void
-get_model_done (MMSerialPort *port,
- GString *response,
- GError *error,
- gpointer user_data)
+pin_puk_recheck_done (MMModem *modem, GError *error, gpointer user_data);
+
+static gboolean
+pin_puk_recheck_again (gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- const char *resp = strip_tag (response->str, GMM_RESP_TAG);
- if (!error)
- mm_callback_info_set_data (info, "card-info-model", g_strdup (resp), g_free);
- else if (!info->error)
- info->error = g_error_copy (error);
+ MM_GENERIC_GSM_GET_PRIVATE (info->modem)->pin_check_timeout = 0;
+ check_pin (MM_GENERIC_GSM (info->modem), pin_puk_recheck_done, info);
+ return FALSE;
}
static void
-get_manufacturer_done (MMSerialPort *port,
- GString *response,
- GError *error,
- gpointer user_data)
+pin_puk_recheck_done (MMModem *modem, GError *error, gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- const char *resp = strip_tag (response->str, GMI_RESP_TAG);
+ MMSerialPort *port;
- if (!error)
- mm_callback_info_set_data (info, "card-info-manufacturer", g_strdup (resp), g_free);
- else
- info->error = g_error_copy (error);
-}
+ /* Clear the pin check timeout to ensure that it won't ever get a
+ * stale MMCallbackInfo if the modem got removed. We'll reschedule it here
+ * anyway if needed.
+ */
+ if (info->modem) {
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
-static void
-get_card_info (MMModem *modem,
- MMModemInfoFn callback,
- gpointer user_data)
-{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
- MMCallbackInfo *info;
+ if (priv->pin_check_timeout)
+ g_source_remove (priv->pin_check_timeout);
+ priv->pin_check_timeout = 0;
+ }
- info = mm_callback_info_new_full (MM_MODEM (modem),
- card_info_invoke,
- G_CALLBACK (callback),
- user_data);
+ /* modem could have been removed before we get here, in which case
+ * 'modem' will be NULL.
+ */
+ info->error = mm_modem_check_removed (modem, error);
+
+ /* If the modem wasn't removed, and the modem isn't ready yet, ask it for
+ * the current PIN status a few times since some devices take a bit to fully
+ * enable themselves after a SIM PIN/PUK unlock.
+ */
+ if ( info->modem
+ && info->error
+ && !g_error_matches (info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_REMOVED)) {
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
- mm_serial_port_queue_command_cached (priv->primary, "+CGMI", 3, get_manufacturer_done, info);
- mm_serial_port_queue_command_cached (priv->primary, "+CGMM", 3, get_model_done, info);
- mm_serial_port_queue_command_cached (priv->primary, "+CGMR", 3, get_version_done, info);
+ if (priv->pin_check_tries < 4) {
+ g_clear_error (&info->error);
+ priv->pin_check_tries++;
+ priv->pin_check_timeout = g_timeout_add_seconds (2, pin_puk_recheck_again, info);
+ return;
+ }
+ }
+
+ /* Otherwise, clean up and return the PIN check result */
+ port = mm_callback_info_get_data (info, PIN_PORT_TAG);
+ if (modem && port)
+ mm_serial_port_close (port);
+
+ mm_callback_info_schedule (info);
}
static void
-send_puk_done (MMSerialPort *port,
+send_puk_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- if (error)
+ if (error) {
info->error = g_error_copy (error);
- mm_callback_info_schedule (info);
+ mm_callback_info_schedule (info);
+ mm_serial_port_close (MM_SERIAL_PORT (port));
+ return;
+ }
+
+ /* Get latest PIN status */
+ MM_GENERIC_GSM_GET_PRIVATE (info->modem)->pin_check_tries = 0;
+ check_pin (MM_GENERIC_GSM (info->modem), pin_puk_recheck_done, info);
}
static void
@@ -767,27 +1459,52 @@ send_puk (MMModemGsmCard *modem,
MMModemFn callback,
gpointer user_data)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
MMCallbackInfo *info;
char *command;
+ MMAtSerialPort *port;
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+
+ /* Ensure we have a usable port to use for the unlock */
+ port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
+ if (!port) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ /* Modem may not be enabled yet, which sometimes can't be done until
+ * the device has been unlocked. In this case we have to open the port
+ * ourselves.
+ */
+ if (!mm_serial_port_open (MM_SERIAL_PORT (port), &info->error)) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+ mm_callback_info_set_data (info, PIN_PORT_TAG, port, NULL);
+
command = g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin);
- mm_serial_port_queue_command (priv->primary, command, 3, send_puk_done, info);
+ mm_at_serial_port_queue_command (port, command, 3, send_puk_done, info);
g_free (command);
}
static void
-send_pin_done (MMSerialPort *port,
+send_pin_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- if (error)
+ if (error) {
info->error = g_error_copy (error);
- mm_callback_info_schedule (info);
+ mm_callback_info_schedule (info);
+ mm_serial_port_close (MM_SERIAL_PORT (port));
+ return;
+ }
+
+ /* Get latest PIN status */
+ MM_GENERIC_GSM_GET_PRIVATE (info->modem)->pin_check_tries = 0;
+ check_pin (MM_GENERIC_GSM (info->modem), pin_puk_recheck_done, info);
}
static void
@@ -796,18 +1513,36 @@ send_pin (MMModemGsmCard *modem,
MMModemFn callback,
gpointer user_data)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
MMCallbackInfo *info;
char *command;
+ MMAtSerialPort *port;
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+
+ /* Ensure we have a usable port to use for the unlock */
+ port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
+ if (!port) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ /* Modem may not be enabled yet, which sometimes can't be done until
+ * the device has been unlocked. In this case we have to open the port
+ * ourselves.
+ */
+ if (!mm_serial_port_open (MM_SERIAL_PORT (port), &info->error)) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+ mm_callback_info_set_data (info, PIN_PORT_TAG, port, NULL);
+
command = g_strdup_printf ("+CPIN=\"%s\"", pin);
- mm_serial_port_queue_command (priv->primary, command, 3, send_pin_done, info);
+ mm_at_serial_port_queue_command (port, command, 3, send_pin_done, info);
g_free (command);
}
static void
-enable_pin_done (MMSerialPort *port,
+enable_pin_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -832,12 +1567,12 @@ enable_pin (MMModemGsmCard *modem,
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
command = g_strdup_printf ("+CLCK=\"SC\",%d,\"%s\"", enabled ? 1 : 0, pin);
- mm_serial_port_queue_command (priv->primary, command, 3, enable_pin_done, info);
+ mm_at_serial_port_queue_command (priv->primary, command, 3, enable_pin_done, info);
g_free (command);
}
static void
-change_pin_done (MMSerialPort *port,
+change_pin_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -862,12 +1597,104 @@ change_pin (MMModemGsmCard *modem,
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
command = g_strdup_printf ("+CPWD=\"SC\",\"%s\",\"%s\"", old_pin, new_pin);
- mm_serial_port_queue_command (priv->primary, command, 3, change_pin_done, info);
+ mm_at_serial_port_queue_command (priv->primary, command, 3, change_pin_done, info);
g_free (command);
}
+static void
+get_unlock_retries (MMModemGsmCard *modem,
+ const char *pin_type,
+ MMModemUIntFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data);
+
+ mm_callback_info_set_result (info,
+ GUINT_TO_POINTER (MM_MODEM_GSM_CARD_UNLOCK_RETRIES_NOT_SUPPORTED),
+ NULL);
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+reg_info_updated (MMGenericGsm *self,
+ gboolean update_rs,
+ MMGenericGsmRegType rs_type,
+ MMModemGsmNetworkRegStatus status,
+ gboolean update_code,
+ const char *oper_code,
+ gboolean update_name,
+ const char *oper_name)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ MMModemGsmNetworkRegStatus old_status;
+ gboolean changed = FALSE;
+
+ if (update_rs) {
+ g_return_if_fail ( rs_type == MM_GENERIC_GSM_REG_TYPE_CS
+ || rs_type == MM_GENERIC_GSM_REG_TYPE_PS);
+
+ old_status = gsm_reg_status (self);
+ priv->reg_status[rs_type - 1] = status;
+ if (gsm_reg_status (self) != old_status)
+ changed = TRUE;
+ }
+
+ if (update_code) {
+ if (g_strcmp0 (oper_code, priv->oper_code) != 0) {
+ g_free (priv->oper_code);
+ priv->oper_code = g_strdup (oper_code);
+ changed = TRUE;
+ }
+ }
+
+ if (update_name) {
+ if (g_strcmp0 (oper_name, priv->oper_name) != 0) {
+ g_free (priv->oper_name);
+ priv->oper_name = g_strdup (oper_name);
+ changed = TRUE;
+ }
+ }
+
+ if (changed) {
+ mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (self),
+ gsm_reg_status (self),
+ priv->oper_code,
+ priv->oper_name);
+ }
+}
+
+static void
+convert_operator_from_ucs2 (char **operator)
+{
+ const char *p;
+ char *converted;
+ size_t len;
+
+ g_return_if_fail (operator != NULL);
+ g_return_if_fail (*operator != NULL);
+
+ p = *operator;
+ len = strlen (p);
+
+ /* Len needs to be a multiple of 4 for UCS2 */
+ if ((len < 4) || ((len % 4) != 0))
+ return;
+
+ while (*p) {
+ if (!isxdigit (*p++))
+ return;
+ }
+
+ converted = mm_modem_charset_hex_to_utf8 (*operator, MM_MODEM_CHARSET_UCS2);
+ if (converted) {
+ g_free (*operator);
+ *operator = converted;
+ }
+}
+
static char *
-parse_operator (const char *reply)
+parse_operator (const char *reply, MMModemCharset cur_charset)
{
char *operator = NULL;
@@ -889,52 +1716,53 @@ parse_operator (const char *reply)
g_regex_unref (r);
}
+ /* Some modems (Option & HSO) return the operator name as a hexadecimal
+ * string of the bytes of the operator name as encoded by the current
+ * character set.
+ */
+ if (operator && (cur_charset == MM_MODEM_CHARSET_UCS2))
+ convert_operator_from_ucs2 (&operator);
+
return operator;
}
static void
-read_operator_code_done (MMSerialPort *port,
+read_operator_code_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (user_data);
+ MMGenericGsm *self = MM_GENERIC_GSM (user_data);
char *oper;
- if (error)
- return;
-
- oper = parse_operator (response->str);
- if (!oper)
- return;
-
- g_free (priv->oper_code);
- priv->oper_code = oper;
+ if (!error) {
+ oper = parse_operator (response->str, MM_MODEM_CHARSET_UNKNOWN);
+ if (oper) {
+ reg_info_updated (self, FALSE, MM_GENERIC_GSM_REG_TYPE_UNKNOWN, 0,
+ TRUE, oper,
+ FALSE, NULL);
+ }
+ }
}
static void
-read_operator_name_done (MMSerialPort *port,
+read_operator_name_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (user_data);
+ MMGenericGsm *self = MM_GENERIC_GSM (user_data);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
char *oper;
- if (error)
- return;
-
- oper = parse_operator (response->str);
- if (!oper)
- return;
-
- g_free (priv->oper_name);
- priv->oper_name = oper;
-
- mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (user_data),
- priv->reg_status,
- priv->oper_code,
- priv->oper_name);
+ if (!error) {
+ oper = parse_operator (response->str, priv->cur_charset);
+ if (oper) {
+ reg_info_updated (self, FALSE, MM_GENERIC_GSM_REG_TYPE_UNKNOWN, 0,
+ FALSE, NULL,
+ TRUE, oper);
+ }
+ }
}
/* Registration */
@@ -961,8 +1789,93 @@ mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem)
}
}
+static void
+got_signal_quality (MMModem *modem,
+ guint32 quality,
+ GError *error,
+ gpointer user_data)
+{
+ mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (modem), quality);
+}
+
+static void
+roam_disconnect_done (MMModem *modem,
+ GError *error,
+ gpointer user_data)
+{
+ g_message ("Disconnected because roaming is not allowed");
+}
+
+static void
+get_reg_act_done (MMModem *modem,
+ guint32 act,
+ GError *error,
+ gpointer user_data)
+{
+ if (modem && !error && act)
+ mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (modem), act);
+}
+
+void
+mm_generic_gsm_set_reg_status (MMGenericGsm *self,
+ MMGenericGsmRegType rs_type,
+ MMModemGsmNetworkRegStatus status)
+{
+ MMGenericGsmPrivate *priv;
+ MMAtSerialPort *port;
+
+ g_return_if_fail (MM_IS_GENERIC_GSM (self));
+
+ g_return_if_fail ( rs_type == MM_GENERIC_GSM_REG_TYPE_CS
+ || rs_type == MM_GENERIC_GSM_REG_TYPE_PS);
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ if (priv->reg_status[rs_type - 1] == status)
+ return;
+
+ g_debug ("%s registration state changed: %d",
+ (rs_type == MM_GENERIC_GSM_REG_TYPE_CS) ? "CS" : "PS",
+ status);
+ priv->reg_status[rs_type - 1] = status;
+
+ port = mm_generic_gsm_get_best_at_port (self, NULL);
+
+ if (status == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME ||
+ status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) {
+
+ /* If we're connected and we're not supposed to roam, but the device
+ * just roamed, disconnect the connection to avoid charging the user
+ * loads of money.
+ */
+ if ( (status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING)
+ && (mm_modem_get_state (MM_MODEM (self)) == MM_MODEM_STATE_CONNECTED)
+ && (priv->roam_allowed == FALSE)) {
+ mm_modem_disconnect (MM_MODEM (self), roam_disconnect_done, NULL);
+ } else {
+ /* Grab the new operator name and MCC/MNC */
+ if (port) {
+ mm_at_serial_port_queue_command (port, "+COPS=3,2;+COPS?", 3, read_operator_code_done, self);
+ mm_at_serial_port_queue_command (port, "+COPS=3,0;+COPS?", 3, read_operator_name_done, self);
+ }
+
+ /* And update signal quality and access technology */
+ mm_modem_gsm_network_get_signal_quality (MM_MODEM_GSM_NETWORK (self), got_signal_quality, NULL);
+ if (MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology)
+ MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology (self, get_reg_act_done, NULL);
+ }
+ } else
+ reg_info_updated (self, FALSE, rs_type, 0, TRUE, NULL, TRUE, NULL);
+
+ mm_generic_gsm_update_enabled_state (self, TRUE, MM_MODEM_STATE_REASON_NONE);
+}
+
+/* Returns TRUE if the modem is "done", ie has registered or been denied */
static gboolean
-reg_status_updated (MMGenericGsm *self, int new_value, GError **error)
+reg_status_updated (MMGenericGsm *self,
+ MMGenericGsmRegType rs_type,
+ int new_value,
+ GError **error)
{
MMModemGsmNetworkRegStatus status;
gboolean status_done = FALSE;
@@ -991,7 +1904,7 @@ reg_status_updated (MMGenericGsm *self, int new_value, GError **error)
break;
}
- mm_generic_gsm_set_reg_status (self, status);
+ mm_generic_gsm_set_reg_status (self, rs_type, status);
/* Registration has either completed successfully or completely failed */
switch (status) {
@@ -1022,21 +1935,35 @@ reg_status_updated (MMGenericGsm *self, int new_value, GError **error)
return status_done;
}
+static MMGenericGsmRegType
+cgreg_to_reg_type (gboolean cgreg)
+{
+ return (cgreg ? MM_GENERIC_GSM_REG_TYPE_PS : MM_GENERIC_GSM_REG_TYPE_CS);
+}
+
static void
-reg_state_changed (MMSerialPort *port,
+reg_state_changed (MMAtSerialPort *port,
GMatchInfo *match_info,
gpointer user_data)
{
MMGenericGsm *self = MM_GENERIC_GSM (user_data);
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
- char *str;
- gboolean done;
+ guint32 state = 0, idx;
+ gulong lac = 0, cell_id = 0;
+ gint act = -1;
+ gboolean cgreg = FALSE;
+ GError *error = NULL;
- str = g_match_info_fetch (match_info, 1);
- done = reg_status_updated (self, atoi (str), NULL);
- g_free (str);
+ if (!mm_gsm_parse_creg_response (match_info, &state, &lac, &cell_id, &act, &cgreg, &error)) {
+ if (mm_options_debug ()) {
+ g_warning ("%s: error parsing unsolicited registration: %s",
+ __func__,
+ error && error->message ? error->message : "(unknown)");
+ }
+ return;
+ }
- if (done) {
+ if (reg_status_updated (self, cgreg_to_reg_type (cgreg), state, NULL)) {
/* If registration is finished (either registered or failed) but the
* registration query hasn't completed yet, just remove the timeout and
* let the registration query complete.
@@ -1046,6 +1973,14 @@ reg_state_changed (MMSerialPort *port,
priv->pending_reg_id = 0;
}
}
+
+ idx = cgreg ? 1 : 0;
+ priv->lac[idx] = lac;
+ priv->cell_id[idx] = cell_id;
+
+ /* Only update access technology if it appeared in the CREG/CGREG response */
+ if (act != -1)
+ mm_generic_gsm_update_access_technology (self, etsi_act_to_mm_act (act));
}
static gboolean
@@ -1072,8 +2007,62 @@ reg_status_again_remove (gpointer data)
g_source_remove (id);
}
+static gboolean
+handle_reg_status_response (MMGenericGsm *self,
+ GString *response,
+ GError **error)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ GMatchInfo *match_info;
+ guint32 status = 0, idx;
+ gulong lac = 0, ci = 0;
+ gint act = -1;
+ gboolean cgreg = FALSE;
+ guint i;
+
+ /* Try to match the response */
+ for (i = 0; i < priv->reg_regex->len; i++) {
+ GRegex *r = g_ptr_array_index (priv->reg_regex, i);
+
+ if (g_regex_match (r, response->str, 0, &match_info))
+ break;
+ g_match_info_free (match_info);
+ match_info = NULL;
+ }
+
+ if (!match_info) {
+ g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Unknown registration status response");
+ return FALSE;
+ }
+
+ /* And parse it */
+ if (!mm_gsm_parse_creg_response (match_info, &status, &lac, &ci, &act, &cgreg, error)) {
+ g_match_info_free (match_info);
+ return FALSE;
+ }
+
+ /* Success; update cached location information */
+ idx = cgreg ? 1 : 0;
+ priv->lac[idx] = lac;
+ priv->cell_id[idx] = ci;
+
+ /* Only update access technology if it appeared in the CREG/CGREG response */
+ if (act != -1)
+ mm_generic_gsm_update_access_technology (self, etsi_act_to_mm_act (act));
+
+ if (status >= 0) {
+ /* Update cached registration status */
+ reg_status_updated (self, cgreg_to_reg_type (cgreg), status, NULL);
+ }
+
+ return TRUE;
+}
+
+#define CGREG_TRIED_TAG "cgreg-tried"
+
static void
-get_reg_status_done (MMSerialPort *port,
+get_reg_status_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -1081,76 +2070,71 @@ get_reg_status_done (MMSerialPort *port,
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
MMGenericGsm *self = MM_GENERIC_GSM (info->modem);
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
- int reg_status = -1;
- GRegex *r;
- GMatchInfo *match_info;
- char *tmp;
guint id;
+ MMModemGsmNetworkRegStatus status;
- g_warn_if_fail (info == priv->pending_reg_info);
+ /* This function should only get called during the connect sequence when
+ * polling for registration state, since explicit registration requests
+ * from D-Bus clients are filled from the cached registration state.
+ */
+ g_return_if_fail (info == priv->pending_reg_info);
if (error) {
- info->error = g_error_copy (error);
- goto reg_done;
- }
-
- r = g_regex_new ("\\+CREG:\\s*(\\d+),\\s*(\\d+)",
- G_REGEX_RAW | G_REGEX_OPTIMIZE,
- 0, &info->error);
- if (r) {
- g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error);
- if (g_match_info_matches (match_info)) {
- /* Get reg status */
- tmp = g_match_info_fetch (match_info, 2);
- if (isdigit (tmp[0])) {
- tmp[1] = '\0';
- reg_status = atoi (tmp);
- } else {
- info->error = g_error_new (MM_MODEM_ERROR,
- MM_MODEM_ERROR_GENERAL,
- "Unknown registration status '%s'",
- tmp);
- }
- g_free (tmp);
+ gboolean cgreg_tried = !!mm_callback_info_get_data (info, CGREG_TRIED_TAG);
+
+ /* If this was a +CREG error, try +CGREG. Some devices (blackberries)
+ * respond to +CREG with an error but return a valid +CGREG response.
+ * So try both. If we get an error from both +CREG and +CGREG, that's
+ * obviously a hard fail.
+ */
+ if (cgreg_tried == FALSE) {
+ mm_callback_info_set_data (info, CGREG_TRIED_TAG, GUINT_TO_POINTER (TRUE), NULL);
+ mm_at_serial_port_queue_command (port, "+CGREG?", 10, get_reg_status_done, info);
+ return;
} else {
- info->error = g_error_new_literal (MM_MODEM_ERROR,
- MM_MODEM_ERROR_GENERAL,
- "Could not parse the registration status response");
+ info->error = g_error_copy (error);
+ goto reg_done;
}
- g_match_info_free (match_info);
- g_regex_unref (r);
}
- if ( reg_status >= 0
- && !reg_status_updated (self, reg_status, &info->error)
- && priv->pending_reg_info) {
- g_clear_error (&info->error);
+ /* The unsolicited registration state handlers will intercept the CREG
+ * response and update the cached registration state for us, so we usually
+ * just need to check the cached state here.
+ */
+
+ if (strlen (response->str)) {
+ /* But just in case the unsolicited handlers doesn't do it... */
+ if (!handle_reg_status_response (self, response, &info->error))
+ goto reg_done;
+ }
- /* Not registered yet; poll registration status again */
+ status = gsm_reg_status (self);
+ if ( status != MM_MODEM_GSM_NETWORK_REG_STATUS_HOME
+ && status != MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING
+ && status != MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED) {
+ /* If we're still waiting for automatic registration to complete or
+ * fail, check again in a few seconds.
+ */
id = g_timeout_add_seconds (1, reg_status_again, info);
mm_callback_info_set_data (info, REG_STATUS_AGAIN_TAG,
- GUINT_TO_POINTER (id),
- reg_status_again_remove);
+ GUINT_TO_POINTER (id),
+ reg_status_again_remove);
return;
}
reg_done:
- /* This will schedule the callback for us */
+ /* This will schedule the pending registration's the callback for us */
mm_generic_gsm_pending_registration_stop (self);
}
static void
-get_registration_status (MMSerialPort *port, MMCallbackInfo *info)
+get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
-
- g_warn_if_fail (info == priv->pending_reg_info);
-
- mm_serial_port_queue_command (port, "+CREG?", 10, get_reg_status_done, info);
+ mm_at_serial_port_queue_command (port, "+CREG?", 10, get_reg_status_done, info);
}
static void
-register_done (MMSerialPort *port,
+register_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -1169,7 +2153,9 @@ register_done (MMSerialPort *port,
if (priv->pending_reg_info) {
g_warn_if_fail (info == priv->pending_reg_info);
- /* Ignore errors here, get the actual registration status */
+ /* Don't use cached registration state here since it could be up to
+ * 30 seconds old. Get fresh registration state.
+ */
get_registration_status (port, info);
}
}
@@ -1182,7 +2168,16 @@ registration_timed_out (gpointer data)
g_warn_if_fail (info == priv->pending_reg_info);
- priv->reg_status = MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE;
+ /* Clear out circuit-switched registration info... */
+ reg_info_updated (MM_GENERIC_GSM (info->modem),
+ TRUE, MM_GENERIC_GSM_REG_TYPE_CS, MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE,
+ TRUE, NULL,
+ TRUE, NULL);
+ /* ... and packet-switched registration info */
+ reg_info_updated (MM_GENERIC_GSM (info->modem),
+ TRUE, MM_GENERIC_GSM_REG_TYPE_PS, MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE,
+ TRUE, NULL,
+ TRUE, NULL);
info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_TIMEOUT);
mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (info->modem));
@@ -1190,28 +2185,47 @@ registration_timed_out (gpointer data)
return FALSE;
}
+static gboolean
+reg_is_idle (MMModemGsmNetworkRegStatus status)
+{
+ if ( status == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME
+ || status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING
+ || status == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING)
+ return FALSE;
+ return TRUE;
+}
+
static void
do_register (MMModemGsmNetwork *modem,
const char *network_id,
MMModemFn callback,
gpointer user_data)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMGenericGsm *self = MM_GENERIC_GSM (modem);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
MMCallbackInfo *info;
- char *command;
+ char *command = NULL;
/* Clear any previous registration */
- mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem));
+ mm_generic_gsm_pending_registration_stop (self);
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
priv->pending_reg_id = g_timeout_add_seconds (60, registration_timed_out, info);
priv->pending_reg_info = info;
- if (network_id)
+ /* If the user sent a specific network to use, lock it in. If no specific
+ * network was given, and the modem is not registered and not searching,
+ * kick it to search for a network. Also do auto registration if the modem
+ * had been set to manual registration last time but now is not.
+ */
+ if (network_id) {
command = g_strdup_printf ("+COPS=1,2,\"%s\"", network_id);
- else
+ priv->manual_reg = TRUE;
+ } else if (reg_is_idle (gsm_reg_status (self)) || priv->manual_reg) {
command = g_strdup ("+COPS=0,,");
+ priv->manual_reg = FALSE;
+ }
/* Ref the callback info to ensure it stays alive for register_done() even
* if the timeout triggers and ends registration (which calls the callback
@@ -1231,8 +2245,12 @@ do_register (MMModemGsmNetwork *modem,
* the +COPS response is never received.
*/
mm_callback_info_ref (info);
- mm_serial_port_queue_command (priv->primary, command, 120, register_done, info);
- g_free (command);
+
+ if (command) {
+ mm_at_serial_port_queue_command (priv->primary, command, 120, register_done, info);
+ g_free (command);
+ } else
+ register_done (priv->primary, NULL, NULL, info);
}
static void
@@ -1242,7 +2260,7 @@ gsm_network_reg_info_invoke (MMCallbackInfo *info)
MMModemGsmNetworkRegInfoFn callback = (MMModemGsmNetworkRegInfoFn) info->callback;
callback (MM_MODEM_GSM_NETWORK (info->modem),
- priv->reg_status,
+ gsm_reg_status (MM_GENERIC_GSM (info->modem)),
priv->oper_code,
priv->oper_name,
info->error,
@@ -1260,7 +2278,9 @@ get_registration_info (MMModemGsmNetwork *self,
gsm_network_reg_info_invoke,
G_CALLBACK (callback),
user_data);
-
+ /* Registration info updates are handled internally either by unsolicited
+ * updates or by polling. Thus just return the cached registration state.
+ */
mm_callback_info_schedule (info);
}
@@ -1292,7 +2312,7 @@ mm_generic_gsm_connect_complete (MMGenericGsm *modem,
}
static void
-connect_report_done (MMSerialPort *port,
+connect_report_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -1323,7 +2343,7 @@ connect_report_done (MMSerialPort *port,
}
static void
-connect_done (MMSerialPort *port,
+connect_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -1335,7 +2355,7 @@ connect_done (MMSerialPort *port,
info->error = g_error_copy (error);
/* Try to get more information why it failed */
priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
- mm_serial_port_queue_command (priv->primary, "+CEER", 3, connect_report_done, info);
+ mm_at_serial_port_queue_command (priv->primary, "+CEER", 3, connect_report_done, info);
} else
mm_generic_gsm_connect_complete (MM_GENERIC_GSM (info->modem), NULL, info);
}
@@ -1369,21 +2389,21 @@ connect (MMModem *modem,
} else
command = g_strconcat ("DT", number, NULL);
- mm_serial_port_queue_command (priv->primary, command, 60, connect_done, info);
+ mm_at_serial_port_queue_command (priv->primary, command, 60, connect_done, info);
g_free (command);
}
static void
-disconnect_flash_done (MMSerialPort *port,
- GError *error,
- gpointer user_data)
+disconnect_done (MMModem *modem,
+ GError *error,
+ gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
MMModemState prev_state;
- info->error = mm_modem_check_removed (info->modem, error);
+ info->error = mm_modem_check_removed (modem, error);
if (info->error) {
- if (info->modem) {
+ if (info->modem && modem) {
/* Reset old state since the operation failed */
prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_GSM_PREV_STATE_TAG));
mm_modem_set_state (MM_MODEM (info->modem),
@@ -1391,10 +2411,11 @@ disconnect_flash_done (MMSerialPort *port,
MM_MODEM_STATE_REASON_NONE);
}
} else {
- MMGenericGsm *self = MM_GENERIC_GSM (info->modem);
+ MMGenericGsm *self = MM_GENERIC_GSM (modem);
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
mm_port_set_connected (priv->data, FALSE);
+ priv->cid = -1;
mm_generic_gsm_update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE);
}
@@ -1402,16 +2423,138 @@ disconnect_flash_done (MMSerialPort *port,
}
static void
+disconnect_all_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ mm_callback_info_schedule ((MMCallbackInfo *) user_data);
+}
+
+static void
+disconnect_send_cgact (MMAtSerialPort *port,
+ gint cid,
+ MMAtSerialResponseFn callback,
+ gpointer user_data)
+{
+ char *command;
+
+ if (cid >= 0)
+ command = g_strdup_printf ("+CGACT=0,%d", cid);
+ else {
+ /* Disable all PDP contexts */
+ command = g_strdup_printf ("+CGACT=0");
+ }
+
+ mm_at_serial_port_queue_command (port, command, 3, callback, user_data);
+ g_free (command);
+}
+
+#define DISCONNECT_CGACT_DONE_TAG "disconnect-cgact-done"
+
+static void
+disconnect_flash_done (MMSerialPort *port,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMGenericGsmPrivate *priv;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->error) {
+ /* Ignore "NO CARRIER" response when modem disconnects and any flash
+ * failures we might encounter. Other errors are hard errors.
+ */
+ if ( !g_error_matches (info->error, MM_MODEM_CONNECT_ERROR, MM_MODEM_CONNECT_ERROR_NO_CARRIER)
+ && !g_error_matches (info->error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_FLASH_FAILED)) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+ g_clear_error (&info->error);
+ }
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+ mm_port_set_connected (priv->data, FALSE);
+
+ /* Don't bother doing the CGACT again if it was done on a secondary port */
+ if (mm_callback_info_get_data (info, DISCONNECT_CGACT_DONE_TAG))
+ disconnect_all_done (MM_AT_SERIAL_PORT (priv->primary), NULL, NULL, info);
+ else {
+ disconnect_send_cgact (MM_AT_SERIAL_PORT (priv->primary),
+ priv->cid,
+ disconnect_all_done,
+ info);
+ }
+}
+
+static void
+disconnect_secondary_cgact_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+ MMGenericGsm *self;
+ MMGenericGsmPrivate *priv;
+
+ if (!info->modem) {
+ info->error = mm_modem_check_removed (info->modem, error);
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ self = MM_GENERIC_GSM (info->modem);
+ priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ /* Now that we've tried deactivating the PDP context on the secondary
+ * port, continue with flashing the primary port.
+ */
+ if (!error)
+ mm_callback_info_set_data (info, DISCONNECT_CGACT_DONE_TAG, GUINT_TO_POINTER (TRUE), NULL);
+
+ mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disconnect_flash_done, info);
+}
+
+static void
+real_do_disconnect (MMGenericGsm *self,
+ gint cid,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new (MM_MODEM (self), callback, user_data);
+
+ /* If the primary port is connected (with PPP) then try sending the PDP
+ * context deactivation on the secondary port because not all modems will
+ * respond to flashing (since either the modem or the kernel's serial
+ * driver doesn't support it).
+ */
+ if ( mm_port_get_connected (MM_PORT (priv->primary))
+ && priv->secondary
+ && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) {
+ disconnect_send_cgact (MM_AT_SERIAL_PORT (priv->secondary),
+ priv->cid,
+ disconnect_secondary_cgact_done,
+ info);
+ } else {
+ /* Just flash the primary port */
+ mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disconnect_flash_done, info);
+ }
+}
+
+static void
disconnect (MMModem *modem,
MMModemFn callback,
gpointer user_data)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMGenericGsm *self = MM_GENERIC_GSM (modem);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
MMCallbackInfo *info;
MMModemState state;
- /* First, reset the previously used CID */
- mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0);
+ priv->roam_allowed = TRUE;
info = mm_callback_info_new (modem, callback, user_data);
@@ -1423,7 +2566,9 @@ disconnect (MMModem *modem,
NULL);
mm_modem_set_state (modem, MM_MODEM_STATE_DISCONNECTING, MM_MODEM_STATE_REASON_NONE);
- mm_serial_port_flash (priv->primary, 1000, disconnect_flash_done, info);
+
+ g_assert (MM_GENERIC_GSM_GET_CLASS (self)->do_disconnect);
+ MM_GENERIC_GSM_GET_CLASS (self)->do_disconnect (self, priv->cid, disconnect_done, info);
}
static void
@@ -1438,7 +2583,7 @@ gsm_network_scan_invoke (MMCallbackInfo *info)
}
static void
-scan_done (MMSerialPort *port,
+scan_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -1470,30 +2615,33 @@ scan (MMModemGsmNetwork *modem,
G_CALLBACK (callback),
user_data);
- mm_serial_port_queue_command (priv->primary, "+COPS=?", 120, scan_done, info);
+ mm_at_serial_port_queue_command (priv->primary, "+COPS=?", 120, scan_done, info);
}
/* SetApn */
+#define APN_CID_TAG "generic-gsm-cid"
+
static void
-set_apn_done (MMSerialPort *port,
+set_apn_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- if (error)
- info->error = g_error_copy (error);
- else
- mm_generic_gsm_set_cid (MM_GENERIC_GSM (info->modem),
- GPOINTER_TO_UINT (mm_callback_info_get_data (info, "cid")));
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (!info->error) {
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ priv->cid = GPOINTER_TO_INT (mm_callback_info_get_data (info, APN_CID_TAG));
+ }
mm_callback_info_schedule (info);
}
static void
-cid_range_read (MMSerialPort *port,
+cid_range_read (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -1503,7 +2651,7 @@ cid_range_read (MMSerialPort *port,
if (error)
info->error = g_error_copy (error);
- else if (g_str_has_prefix (response->str, "+CGDCONT: ")) {
+ else if (g_str_has_prefix (response->str, "+CGDCONT:")) {
GRegex *r;
GMatchInfo *match_info;
@@ -1550,16 +2698,16 @@ cid_range_read (MMSerialPort *port,
const char *apn = (const char *) mm_callback_info_get_data (info, "apn");
char *command;
- mm_callback_info_set_data (info, "cid", GUINT_TO_POINTER (cid), NULL);
+ mm_callback_info_set_data (info, APN_CID_TAG, GINT_TO_POINTER (cid), NULL);
command = g_strdup_printf ("+CGDCONT=%d,\"IP\",\"%s\"", cid, apn);
- mm_serial_port_queue_command (port, command, 3, set_apn_done, info);
+ mm_at_serial_port_queue_command (port, command, 3, set_apn_done, info);
g_free (command);
}
}
static void
-existing_apns_read (MMSerialPort *port,
+existing_apns_read (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -1567,9 +2715,10 @@ existing_apns_read (MMSerialPort *port,
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
gboolean found = FALSE;
- if (error)
- info->error = g_error_copy (error);
- else if (g_str_has_prefix (response->str, "+CGDCONT: ")) {
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->error)
+ goto done;
+ else if (g_str_has_prefix (response->str, "+CGDCONT:")) {
GRegex *r;
GMatchInfo *match_info;
@@ -1592,7 +2741,7 @@ existing_apns_read (MMSerialPort *port,
apn = g_match_info_fetch (match_info, 3);
if (!strcmp (apn, new_apn)) {
- mm_generic_gsm_set_cid (MM_GENERIC_GSM (info->modem), (guint32) num_cid);
+ MM_GENERIC_GSM_GET_PRIVATE (info->modem)->cid = num_cid;
found = TRUE;
}
@@ -1620,11 +2769,13 @@ existing_apns_read (MMSerialPort *port,
MM_MODEM_ERROR_GENERAL,
"Could not parse the response");
+done:
if (found || info->error)
mm_callback_info_schedule (info);
- else
+ else {
/* APN not configured on the card. Get the allowed CID range */
- mm_serial_port_queue_command_cached (port, "+CGDCONT=?", 3, cid_range_read, info);
+ mm_at_serial_port_queue_command_cached (port, "+CGDCONT=?", 3, cid_range_read, info);
+ }
}
static void
@@ -1640,31 +2791,96 @@ set_apn (MMModemGsmNetwork *modem,
mm_callback_info_set_data (info, "apn", g_strdup (apn), g_free);
/* Start by searching if the APN is already in card */
- mm_serial_port_queue_command (priv->primary, "+CGDCONT?", 3, existing_apns_read, info);
+ mm_at_serial_port_queue_command (priv->primary, "+CGDCONT?", 3, existing_apns_read, info);
}
/* GetSignalQuality */
+static gboolean
+emit_signal_quality_change (gpointer user_data)
+{
+ MMGenericGsm *self = MM_GENERIC_GSM (user_data);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ priv->signal_quality_id = 0;
+ priv->signal_quality_timestamp = time (NULL);
+ mm_modem_gsm_network_signal_quality (MM_MODEM_GSM_NETWORK (self), priv->signal_quality);
+ return FALSE;
+}
+
+void
+mm_generic_gsm_update_signal_quality (MMGenericGsm *self, guint32 quality)
+{
+ MMGenericGsmPrivate *priv;
+ guint delay = 0;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (MM_IS_GENERIC_GSM (self));
+ g_return_if_fail (quality <= 100);
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ if (priv->signal_quality == quality)
+ return;
+
+ priv->signal_quality = quality;
+
+ /* Some modems will send unsolcited signal quality changes quite often,
+ * so rate-limit them to every few seconds. Track the last time we
+ * emitted signal quality so that we send the signal immediately if there
+ * haven't been any updates in a while.
+ */
+ if (!priv->signal_quality_id) {
+ if (priv->signal_quality_timestamp > 0) {
+ time_t curtime;
+ long int diff;
+
+ curtime = time (NULL);
+ diff = curtime - priv->signal_quality_timestamp;
+ if (diff == 0) {
+ /* If the device is sending more than one update per second,
+ * make sure we don't spam clients with signals.
+ */
+ delay = 3;
+ } else if ((diff > 0) && (diff <= 3)) {
+ /* Emitted an update less than 3 seconds ago; schedule an update
+ * 3 seconds after the previous one.
+ */
+ delay = (guint) diff;
+ } else {
+ /* Otherwise, we haven't emitted an update in the last 3 seconds,
+ * or the user turned their clock back, or something like that.
+ */
+ delay = 0;
+ }
+ }
+
+ priv->signal_quality_id = g_timeout_add_seconds (delay,
+ emit_signal_quality_change,
+ self);
+ }
+}
+
static void
-get_signal_quality_done (MMSerialPort *port,
+get_signal_quality_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
- MMGenericGsmPrivate *priv;
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
char *reply = response->str;
+ gboolean parsed = FALSE;
- if (error)
- info->error = g_error_copy (error);
- else if (!strncmp (reply, "+CSQ: ", 6)) {
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->error)
+ goto done;
+
+ if (!strncmp (reply, "+CSQ: ", 6)) {
/* Got valid reply */
int quality;
int ber;
- reply += 6;
-
- if (sscanf (reply, "%d, %d", &quality, &ber)) {
+ if (sscanf (reply + 6, "%d, %d", &quality, &ber)) {
/* 99 means unknown */
if (quality == 99) {
info->error = g_error_new_literal (MM_MOBILE_ERROR,
@@ -1674,15 +2890,19 @@ get_signal_quality_done (MMSerialPort *port,
/* Normalize the quality */
quality = CLAMP (quality, 0, 31) * 100 / 31;
- priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
- priv->signal_quality = quality;
+ mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (info->modem), quality);
mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL);
}
- } else
- info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
- "Could not parse signal quality results");
+ parsed = TRUE;
+ }
}
+ if (!parsed && !info->error) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Could not parse signal quality results");
+ }
+
+done:
mm_callback_info_schedule (info);
}
@@ -1693,24 +2913,362 @@ get_signal_quality (MMModemGsmNetwork *modem,
{
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
MMCallbackInfo *info;
- gboolean connected;
+ MMAtSerialPort *port;
+
+ info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data);
+
+ port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), NULL);
+ if (port)
+ mm_at_serial_port_queue_command (port, "+CSQ", 3, get_signal_quality_done, info);
+ else {
+ /* Use cached signal quality */
+ mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->signal_quality), NULL);
+ mm_callback_info_schedule (info);
+ }
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ MMModemGsmAccessTech mm_act;
+ gint etsi_act;
+} ModeEtsi;
+
+static ModeEtsi modes_table[] = {
+ { MM_MODEM_GSM_ACCESS_TECH_GSM, 0 },
+ { MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT, 1 },
+ { MM_MODEM_GSM_ACCESS_TECH_UMTS, 2 },
+ { MM_MODEM_GSM_ACCESS_TECH_EDGE, 3 },
+ { MM_MODEM_GSM_ACCESS_TECH_HSDPA, 4 },
+ { MM_MODEM_GSM_ACCESS_TECH_HSUPA, 5 },
+ { MM_MODEM_GSM_ACCESS_TECH_HSPA, 6 },
+ { MM_MODEM_GSM_ACCESS_TECH_HSPA, 7 }, /* E-UTRAN/LTE => HSPA for now */
+ { MM_MODEM_GSM_ACCESS_TECH_UNKNOWN, -1 },
+};
+
+static MMModemGsmAccessTech
+etsi_act_to_mm_act (gint act)
+{
+ ModeEtsi *iter = &modes_table[0];
+
+ while (iter->mm_act != MM_MODEM_GSM_ACCESS_TECH_UNKNOWN) {
+ if (iter->etsi_act == act)
+ return iter->mm_act;
+ iter++;
+ }
+ return MM_MODEM_GSM_ACCESS_TECH_UNKNOWN;
+}
+
+static void
+_internal_update_access_technology (MMGenericGsm *modem,
+ MMModemGsmAccessTech act)
+{
+ MMGenericGsmPrivate *priv;
+
+ g_return_if_fail (modem != NULL);
+ g_return_if_fail (MM_IS_GENERIC_GSM (modem));
+ g_return_if_fail (act >= MM_MODEM_GSM_ACCESS_TECH_UNKNOWN && act <= MM_MODEM_GSM_ACCESS_TECH_LAST);
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+
+ if (act != priv->act) {
+ MMModemDeprecatedMode old_mode;
+
+ priv->act = act;
+ g_object_notify (G_OBJECT (modem), MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY);
+
+ /* Deprecated value */
+ old_mode = mm_modem_gsm_network_act_to_old_mode (act);
+ g_signal_emit_by_name (G_OBJECT (modem), "network-mode", old_mode);
+ }
+}
+
+void
+mm_generic_gsm_update_access_technology (MMGenericGsm *self,
+ MMModemGsmAccessTech act)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (MM_IS_GENERIC_GSM (self));
+
+ /* For plugins, don't update the access tech when the modem isn't enabled */
+ if (mm_modem_get_state (MM_MODEM (self)) >= MM_MODEM_STATE_ENABLED)
+ _internal_update_access_technology (self, act);
+}
+
+void
+mm_generic_gsm_update_allowed_mode (MMGenericGsm *self,
+ MMModemGsmAllowedMode mode)
+{
+ MMGenericGsmPrivate *priv;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (MM_IS_GENERIC_GSM (self));
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ if (mode != priv->allowed_mode) {
+ priv->allowed_mode = mode;
+ g_object_notify (G_OBJECT (self), MM_MODEM_GSM_NETWORK_ALLOWED_MODE);
+ }
+}
+
+static void
+set_allowed_mode_done (MMModem *modem, GError *error, gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (!info->error) {
+ MMModemGsmAllowedMode mode = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "mode"));
- connected = mm_port_get_connected (MM_PORT (priv->primary));
- if (connected && !priv->secondary) {
- g_message ("Returning saved signal quality %d", priv->signal_quality);
- callback (MM_MODEM (modem), priv->signal_quality, NULL, user_data);
+ mm_generic_gsm_update_allowed_mode (MM_GENERIC_GSM (info->modem), mode);
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+set_allowed_mode (MMModemGsmNetwork *net,
+ MMModemGsmAllowedMode mode,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsm *self = MM_GENERIC_GSM (net);
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new (MM_MODEM (self), callback, user_data);
+
+ switch (mode) {
+ case MM_MODEM_GSM_ALLOWED_MODE_ANY:
+ case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED:
+ case MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED:
+ case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY:
+ case MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY:
+ if (!MM_GENERIC_GSM_GET_CLASS (self)->set_allowed_mode) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
+ "Operation not supported");
+ } else {
+ mm_callback_info_set_data (info, "mode", GUINT_TO_POINTER (mode), NULL);
+ MM_GENERIC_GSM_GET_CLASS (self)->set_allowed_mode (self, mode, set_allowed_mode_done, info);
+ }
+ break;
+ default:
+ info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Invalid mode.");
+ break;
+ }
+
+ if (info->error)
+ mm_callback_info_schedule (info);
+}
+
+/*****************************************************************************/
+/* Charset stuff */
+
+static void
+get_charsets_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMGenericGsmPrivate *priv;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->error) {
+ mm_callback_info_schedule (info);
return;
}
- info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data);
- mm_serial_port_queue_command (connected ? priv->secondary : priv->primary, "+CSQ", 3, get_signal_quality_done, info);
+ priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ priv->charsets = MM_MODEM_CHARSET_UNKNOWN;
+ if (!mm_gsm_parse_cscs_support_response (response->str, &priv->charsets)) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Failed to parse the supported character sets response");
+ } else
+ mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->charsets), NULL);
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+get_supported_charsets (MMModem *modem,
+ MMModemUIntFn callback,
+ gpointer user_data)
+{
+ MMGenericGsm *self = MM_GENERIC_GSM (modem);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ MMCallbackInfo *info;
+ MMAtSerialPort *port;
+
+ info = mm_callback_info_uint_new (MM_MODEM (self), callback, user_data);
+
+ /* Use cached value if we have one */
+ if (priv->charsets) {
+ mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->charsets), NULL);
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ /* Otherwise hit up the modem */
+ port = mm_generic_gsm_get_best_at_port (self, &info->error);
+ if (!port) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ mm_at_serial_port_queue_command (port, "+CSCS=?", 3, get_charsets_done, info);
+}
+
+static void
+set_get_charset_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMGenericGsmPrivate *priv;
+ MMModemCharset tried_charset;
+ const char *p;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->error) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ p = response->str;
+ if (g_str_has_prefix (p, "+CSCS:"))
+ p += 6;
+ while (*p == ' ')
+ p++;
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+ priv->cur_charset = mm_modem_charset_from_string (p);
+
+ tried_charset = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "charset"));
+
+ if (tried_charset != priv->cur_charset) {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_UNSUPPORTED_CHARSET,
+ "Modem failed to change character set to %s",
+ mm_modem_charset_to_string (tried_charset));
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+#define TRIED_NO_QUOTES_TAG "tried-no-quotes"
+
+static void
+set_charset_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->error) {
+ gboolean tried_no_quotes = !!mm_callback_info_get_data (info, TRIED_NO_QUOTES_TAG);
+ MMModemCharset charset = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "charset"));
+ char *command;
+
+ if (!info->modem || tried_no_quotes) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ /* Some modems puke if you include the quotes around the character
+ * set name, so lets try it again without them.
+ */
+ mm_callback_info_set_data (info, TRIED_NO_QUOTES_TAG, GUINT_TO_POINTER (TRUE), NULL);
+ command = g_strdup_printf ("+CSCS=%s", mm_modem_charset_to_string (charset));
+ mm_at_serial_port_queue_command (port, command, 3, set_charset_done, info);
+ g_free (command);
+ } else
+ mm_at_serial_port_queue_command (port, "+CSCS?", 3, set_get_charset_done, info);
+}
+
+static gboolean
+check_for_single_value (guint32 value)
+{
+ gboolean found = FALSE;
+ guint32 i;
+
+ for (i = 1; i <= 32; i++) {
+ if (value & 0x1) {
+ if (found)
+ return FALSE; /* More than one bit set */
+ found = TRUE;
+ }
+ value >>= 1;
+ }
+
+ return TRUE;
+}
+
+static void
+set_charset (MMModem *modem,
+ MMModemCharset charset,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ const char *str;
+ char *command;
+ MMAtSerialPort *port;
+
+ info = mm_callback_info_new (modem, callback, user_data);
+
+ if (!(priv->charsets & charset) || !check_for_single_value (charset)) {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_UNSUPPORTED_CHARSET,
+ "Character set 0x%X not supported",
+ charset);
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ str = mm_modem_charset_to_string (charset);
+ if (!str) {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_UNSUPPORTED_CHARSET,
+ "Unhandled character set 0x%X",
+ charset);
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
+ if (!port) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ mm_callback_info_set_data (info, "charset", GUINT_TO_POINTER (charset), NULL);
+
+ command = g_strdup_printf ("+CSCS=\"%s\"", str);
+ mm_at_serial_port_queue_command (port, command, 3, set_charset_done, info);
+ g_free (command);
+}
+
+MMModemCharset
+mm_generic_gsm_get_charset (MMGenericGsm *self)
+{
+ g_return_val_if_fail (self != NULL, MM_MODEM_CHARSET_UNKNOWN);
+ g_return_val_if_fail (MM_IS_GENERIC_GSM (self), MM_MODEM_CHARSET_UNKNOWN);
+
+ return MM_GENERIC_GSM_GET_PRIVATE (self)->cur_charset;
}
/*****************************************************************************/
/* MMModemGsmSms interface */
static void
-sms_send_done (MMSerialPort *port,
+sms_send_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
@@ -1733,39 +3291,29 @@ sms_send (MMModemGsmSms *modem,
MMModemFn callback,
gpointer user_data)
{
- MMGenericGsmPrivate *priv;
MMCallbackInfo *info;
char *command;
- gboolean connected;
- MMSerialPort *port = NULL;
+ MMAtSerialPort *port;
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
- priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
- connected = mm_port_get_connected (MM_PORT (priv->primary));
- if (connected)
- port = priv->secondary;
- else
- port = priv->primary;
-
+ port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
if (!port) {
- info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED,
- "Cannot send SMS while connected");
mm_callback_info_schedule (info);
return;
}
/* FIXME: use the PDU mode instead */
- mm_serial_port_queue_command (port, "AT+CMGF=1", 3, NULL, NULL);
+ mm_at_serial_port_queue_command (port, "AT+CMGF=1", 3, NULL, NULL);
command = g_strdup_printf ("+CMGS=\"%s\"\r%s\x1a", number, text);
- mm_serial_port_queue_command (port, command, 10, sms_send_done, info);
+ mm_at_serial_port_queue_command (port, command, 10, sms_send_done, info);
g_free (command);
}
-MMSerialPort *
-mm_generic_gsm_get_port (MMGenericGsm *modem,
- MMPortType ptype)
+MMAtSerialPort *
+mm_generic_gsm_get_at_port (MMGenericGsm *modem,
+ MMPortType ptype)
{
g_return_val_if_fail (MM_IS_GENERIC_GSM (modem), NULL);
g_return_val_if_fail (ptype != MM_PORT_TYPE_UNKNOWN, NULL);
@@ -1778,105 +3326,267 @@ mm_generic_gsm_get_port (MMGenericGsm *modem,
return NULL;
}
+MMAtSerialPort *
+mm_generic_gsm_get_best_at_port (MMGenericGsm *self, GError **error)
+{
+ MMGenericGsmPrivate *priv;
+
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (MM_IS_GENERIC_GSM (self), NULL);
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ if (!mm_port_get_connected (MM_PORT (priv->primary)))
+ return priv->primary;
+
+ if (!priv->secondary) {
+ g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED,
+ "Cannot perform this operation while connected");
+ }
+
+ return priv->secondary;
+}
+
/*****************************************************************************/
/* MMModemSimple interface */
typedef enum {
- SIMPLE_STATE_BEGIN = 0,
+ SIMPLE_STATE_CHECK_PIN = 0,
SIMPLE_STATE_ENABLE,
- SIMPLE_STATE_CHECK_PIN,
+ SIMPLE_STATE_ALLOWED_MODE,
SIMPLE_STATE_REGISTER,
SIMPLE_STATE_SET_APN,
SIMPLE_STATE_CONNECT,
SIMPLE_STATE_DONE
} SimpleState;
-static const char *
-simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error)
+/* Looks a value up in the simple connect properties dictionary. If the
+ * requested key is not present in the dict, NULL is returned. If the
+ * requested key is present but is not a string, an error is returned.
+ */
+static gboolean
+simple_get_property (MMCallbackInfo *info,
+ const char *name,
+ GType expected_type,
+ const char **out_str,
+ guint32 *out_num,
+ gboolean *out_bool,
+ GError **error)
{
GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties");
GValue *value;
+ gint foo;
+
+ g_return_val_if_fail (properties != NULL, FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+ if (out_str)
+ g_return_val_if_fail (*out_str == NULL, FALSE);
value = (GValue *) g_hash_table_lookup (properties, name);
if (!value)
- return NULL;
-
- if (G_VALUE_HOLDS_STRING (value))
- return g_value_get_string (value);
+ return FALSE;
+
+ if ((expected_type == G_TYPE_STRING) && G_VALUE_HOLDS_STRING (value)) {
+ *out_str = g_value_get_string (value);
+ return TRUE;
+ } else if (expected_type == G_TYPE_UINT) {
+ if (G_VALUE_HOLDS_UINT (value)) {
+ *out_num = g_value_get_uint (value);
+ return TRUE;
+ } else if (G_VALUE_HOLDS_INT (value)) {
+ /* handle ints for convenience, but only if they are >= 0 */
+ foo = g_value_get_int (value);
+ if (foo >= 0) {
+ *out_num = (guint) foo;
+ return TRUE;
+ }
+ }
+ } else if (expected_type == G_TYPE_BOOLEAN && G_VALUE_HOLDS_BOOLEAN (value)) {
+ *out_bool = g_value_get_boolean (value);
+ return TRUE;
+ }
g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
- "Invalid property type for '%s': %s (string expected)",
- name, G_VALUE_TYPE_NAME (value));
+ "Invalid property type for '%s': %s (%s expected)",
+ name, G_VALUE_TYPE_NAME (value), g_type_name (expected_type));
- return NULL;
+ return FALSE;
+}
+
+static const char *
+simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error)
+{
+ const char *str = NULL;
+
+ simple_get_property (info, name, G_TYPE_STRING, &str, NULL, NULL, error);
+ return str;
+}
+
+static gboolean
+simple_get_uint_property (MMCallbackInfo *info, const char *name, guint32 *out_val, GError **error)
+{
+ return simple_get_property (info, name, G_TYPE_UINT, NULL, out_val, NULL, error);
+}
+
+static gboolean
+simple_get_bool_property (MMCallbackInfo *info, const char *name, gboolean *out_val, GError **error)
+{
+ return simple_get_property (info, name, G_TYPE_BOOLEAN, NULL, NULL, out_val, error);
+}
+
+static gboolean
+simple_get_allowed_mode (MMCallbackInfo *info,
+ MMModemGsmAllowedMode *out_mode,
+ GError **error)
+{
+ MMModemDeprecatedMode old_mode = MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY;
+ MMModemGsmAllowedMode allowed_mode = MM_MODEM_GSM_ALLOWED_MODE_ANY;
+ GError *tmp_error = NULL;
+
+ /* check for new allowed mode first */
+ if (simple_get_uint_property (info, "allowed_mode", &allowed_mode, &tmp_error)) {
+ if (allowed_mode > MM_MODEM_GSM_ALLOWED_MODE_LAST) {
+ g_set_error (&tmp_error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Invalid allowed mode %d", old_mode);
+ } else {
+ *out_mode = allowed_mode;
+ return TRUE;
+ }
+ } else if (!tmp_error) {
+ /* and if not, the old allowed mode */
+ if (simple_get_uint_property (info, "network_mode", &old_mode, &tmp_error)) {
+ if (old_mode > MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_LAST) {
+ g_set_error (&tmp_error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Invalid allowed mode %d", old_mode);
+ } else {
+ *out_mode = mm_modem_gsm_network_old_mode_to_allowed (old_mode);
+ return TRUE;
+ }
+ }
+ }
+
+ if (error)
+ *error = tmp_error;
+ return FALSE;
}
static void
simple_state_machine (MMModem *modem, GError *error, gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- const char *str;
+ MMGenericGsmPrivate *priv;
+ const char *str, *unlock = NULL;
SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state"));
- gboolean need_pin = FALSE;
+ SimpleState next_state = state;
+ gboolean done = FALSE;
+ MMModemGsmAllowedMode allowed_mode;
+ gboolean home_only = FALSE;
- if (error) {
- if (g_error_matches (error, MM_MOBILE_ERROR, MM_MOBILE_ERROR_SIM_PIN)) {
- need_pin = TRUE;
- state = SIMPLE_STATE_CHECK_PIN;
- } else {
- info->error = g_error_copy (error);
- goto out;
- }
+ info->error = mm_modem_check_removed (modem, error);
+ if (info->error)
+ goto out;
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+
+ if (mm_options_debug ()) {
+ GTimeVal tv;
+ char *data_device;
+
+ g_object_get (G_OBJECT (modem), MM_MODEM_DATA_DEVICE, &data_device, NULL);
+ g_get_current_time (&tv);
+ g_debug ("<%ld.%ld> (%s): simple connect state %d",
+ tv.tv_sec, tv.tv_usec, data_device, state);
+ g_free (data_device);
}
switch (state) {
- case SIMPLE_STATE_BEGIN:
- state = SIMPLE_STATE_ENABLE;
- mm_modem_enable (modem, simple_state_machine, info);
- break;
- case SIMPLE_STATE_ENABLE:
- state = SIMPLE_STATE_CHECK_PIN;
- mm_generic_gsm_check_pin (MM_GENERIC_GSM (modem), simple_state_machine, info);
- break;
case SIMPLE_STATE_CHECK_PIN:
- if (need_pin) {
- str = simple_get_string_property (info, "pin", &info->error);
- if (str)
- mm_modem_gsm_card_send_pin (MM_MODEM_GSM_CARD (modem), str, simple_state_machine, info);
- else
- info->error = g_error_copy (error);
- } else {
- str = simple_get_string_property (info, "network_id", &info->error);
- state = SIMPLE_STATE_REGISTER;
- if (!info->error)
- mm_modem_gsm_network_register (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info);
+ next_state = SIMPLE_STATE_ENABLE;
+
+ /* If we need a PIN, send it now, but we don't care about SIM-PIN2/SIM-PUK2
+ * since the device is operational without it.
+ */
+ unlock = mm_modem_base_get_unlock_required (MM_MODEM_BASE (modem));
+ if (unlock && strcmp (unlock, "sim-puk2") && strcmp (unlock, "sim-pin2")) {
+ gboolean success = FALSE;
+
+ if (!strcmp (unlock, "sim-pin")) {
+ str = simple_get_string_property (info, "pin", &info->error);
+ if (str) {
+ mm_modem_gsm_card_send_pin (MM_MODEM_GSM_CARD (modem), str, simple_state_machine, info);
+ success = TRUE;
+ }
+ }
+ if (!success && !info->error)
+ info->error = error_for_unlock_required (unlock);
+ break;
}
+ /* Fall through if no PIN required */
+ case SIMPLE_STATE_ENABLE:
+ next_state = SIMPLE_STATE_ALLOWED_MODE;
+ mm_modem_enable (modem, simple_state_machine, info);
break;
+ case SIMPLE_STATE_ALLOWED_MODE:
+ next_state = SIMPLE_STATE_REGISTER;
+ if ( simple_get_allowed_mode (info, &allowed_mode, &info->error)
+ && (allowed_mode != priv->allowed_mode)) {
+ mm_modem_gsm_network_set_allowed_mode (MM_MODEM_GSM_NETWORK (modem),
+ allowed_mode,
+ simple_state_machine,
+ info);
+ break;
+ } else if (info->error)
+ break;
+ /* otherwise fall through as no allowed mode was sent */
case SIMPLE_STATE_REGISTER:
+ next_state = SIMPLE_STATE_SET_APN;
+ str = simple_get_string_property (info, "network_id", &info->error);
+ if (info->error)
+ str = NULL;
+ mm_modem_gsm_network_register (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info);
+ break;
+ case SIMPLE_STATE_SET_APN:
+ next_state = SIMPLE_STATE_CONNECT;
str = simple_get_string_property (info, "apn", &info->error);
- if (str) {
- state = SIMPLE_STATE_SET_APN;
- mm_modem_gsm_network_set_apn (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info);
+ if (str || info->error) {
+ if (str)
+ mm_modem_gsm_network_set_apn (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info);
break;
}
- /* Fall through */
- case SIMPLE_STATE_SET_APN:
- str = simple_get_string_property (info, "number", &info->error);
- state = SIMPLE_STATE_CONNECT;
- mm_modem_connect (modem, str, simple_state_machine, info);
- break;
+ /* Fall through if no APN or no 'apn' property error */
case SIMPLE_STATE_CONNECT:
- state = SIMPLE_STATE_DONE;
+ next_state = SIMPLE_STATE_DONE;
+ str = simple_get_string_property (info, "number", &info->error);
+ if (!info->error) {
+ if (simple_get_bool_property (info, "home_only", &home_only, &info->error)) {
+ MMModemGsmNetworkRegStatus status;
+
+ priv->roam_allowed = !home_only;
+
+ /* Don't connect if we're not supposed to be roaming */
+ status = gsm_reg_status (MM_GENERIC_GSM (modem));
+ if (home_only && (status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING)) {
+ info->error = g_error_new_literal (MM_MOBILE_ERROR,
+ MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED,
+ "Roaming is not allowed.");
+ break;
+ }
+ } else if (info->error)
+ break;
+
+ mm_modem_connect (modem, str, simple_state_machine, info);
+ }
break;
case SIMPLE_STATE_DONE:
+ done = TRUE;
break;
}
out:
- if (info->error || state == SIMPLE_STATE_DONE)
+ if (info->error || done)
mm_callback_info_schedule (info);
else
- mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (state), NULL);
+ mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (next_state), NULL);
}
static void
@@ -1887,6 +3597,29 @@ simple_connect (MMModemSimple *simple,
{
MMCallbackInfo *info;
+ /* If debugging, list all the simple connect properties */
+ if (mm_options_debug ()) {
+ GHashTableIter iter;
+ gpointer key, value;
+ GTimeVal tv;
+ char *data_device;
+
+ g_object_get (G_OBJECT (simple), MM_MODEM_DATA_DEVICE, &data_device, NULL);
+ g_get_current_time (&tv);
+
+ g_hash_table_iter_init (&iter, properties);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ char *val_str;
+
+ val_str = g_strdup_value_contents ((GValue *) value);
+ g_debug ("<%ld.%ld> (%s): %s => %s",
+ tv.tv_sec, tv.tv_usec,
+ data_device, (const char *) key, val_str);
+ g_free (val_str);
+ }
+ g_free (data_device);
+ }
+
info = mm_callback_info_new (MM_MODEM (simple), callback, user_data);
mm_callback_info_set_data (info, "simple-connect-properties",
g_hash_table_ref (properties),
@@ -1895,8 +3628,6 @@ simple_connect (MMModemSimple *simple,
simple_state_machine (MM_MODEM (simple), NULL, info);
}
-
-
static void
simple_free_gvalue (gpointer data)
{
@@ -1928,16 +3659,22 @@ simple_string_value (const char *str)
return val;
}
+#define SS_HASH_TAG "simple-get-status"
+
static void
simple_status_got_signal_quality (MMModem *modem,
guint32 result,
GError *error,
gpointer user_data)
{
- if (error)
- g_warning ("Error getting signal quality: %s", error->message);
- else
- g_hash_table_insert ((GHashTable *) user_data, "signal_quality", simple_uint_value (result));
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ GHashTable *properties;
+
+ if (!error) {
+ properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG);
+ g_hash_table_insert (properties, "signal_quality", simple_uint_value (result));
+ }
+ mm_callback_info_chain_complete_one (info);
}
static void
@@ -1946,20 +3683,14 @@ simple_status_got_band (MMModem *modem,
GError *error,
gpointer user_data)
{
- /* Ignore band errors since there's no generic implementation for it */
- if (!error)
- g_hash_table_insert ((GHashTable *) user_data, "band", simple_uint_value (result));
-}
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ GHashTable *properties;
-static void
-simple_status_got_mode (MMModem *modem,
- guint32 result,
- GError *error,
- gpointer user_data)
-{
- /* Ignore network mode errors since there's no generic implementation for it */
- if (!error)
- g_hash_table_insert ((GHashTable *) user_data, "network_mode", simple_uint_value (result));
+ if (!error) {
+ properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG);
+ g_hash_table_insert (properties, "band", simple_uint_value (result));
+ }
+ mm_callback_info_chain_complete_one (info);
}
static void
@@ -1973,17 +3704,15 @@ simple_status_got_reg_info (MMModemGsmNetwork *modem,
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
GHashTable *properties;
- if (error)
- info->error = g_error_copy (error);
- else {
- properties = (GHashTable *) mm_callback_info_get_data (info, "simple-get-status");
-
+ info->error = mm_modem_check_removed ((MMModem *) modem, error);
+ if (!info->error) {
+ properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG);
+
g_hash_table_insert (properties, "registration_status", simple_uint_value (status));
g_hash_table_insert (properties, "operator_code", simple_string_value (oper_code));
g_hash_table_insert (properties, "operator_name", simple_string_value (oper_name));
}
-
- mm_callback_info_schedule (info);
+ mm_callback_info_chain_complete_one (info);
}
static void
@@ -1992,7 +3721,7 @@ simple_get_status_invoke (MMCallbackInfo *info)
MMModemSimpleGetStatusFn callback = (MMModemSimpleGetStatusFn) info->callback;
callback (MM_MODEM_SIMPLE (info->modem),
- (GHashTable *) mm_callback_info_get_data (info, "simple-get-status"),
+ (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG),
info->error, info->user_data);
}
@@ -2001,23 +3730,54 @@ simple_get_status (MMModemSimple *simple,
MMModemSimpleGetStatusFn callback,
gpointer user_data)
{
- MMModemGsmNetwork *gsm;
+ MMModemGsmNetwork *gsm = MM_MODEM_GSM_NETWORK (simple);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (simple);
GHashTable *properties;
MMCallbackInfo *info;
+ MMModemDeprecatedMode old_mode;
info = mm_callback_info_new_full (MM_MODEM (simple),
simple_get_status_invoke,
G_CALLBACK (callback),
user_data);
- properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, simple_free_gvalue);
- mm_callback_info_set_data (info, "simple-get-status", properties, (GDestroyNotify) g_hash_table_unref);
+ properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, simple_free_gvalue);
+ mm_callback_info_set_data (info, SS_HASH_TAG, properties, (GDestroyNotify) g_hash_table_unref);
+
+ mm_callback_info_chain_start (info, 3);
+ mm_modem_gsm_network_get_signal_quality (gsm, simple_status_got_signal_quality, info);
+ mm_modem_gsm_network_get_band (gsm, simple_status_got_band, info);
+ mm_modem_gsm_network_get_registration_info (gsm, simple_status_got_reg_info, info);
+
+ if (priv->act > -1) {
+ /* Deprecated key */
+ old_mode = mm_modem_gsm_network_act_to_old_mode (priv->act);
+ g_hash_table_insert (properties, "network_mode", simple_uint_value (old_mode));
+
+ /* New key */
+ g_hash_table_insert (properties, "access_technology", simple_uint_value (priv->act));
+ }
+}
+
+/*****************************************************************************/
+
+static void
+modem_state_changed (MMGenericGsm *self, GParamSpec *pspec, gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ MMModemState state;
- gsm = MM_MODEM_GSM_NETWORK (simple);
- mm_modem_gsm_network_get_signal_quality (gsm, simple_status_got_signal_quality, properties);
- mm_modem_gsm_network_get_band (gsm, simple_status_got_band, properties);
- mm_modem_gsm_network_get_mode (gsm, simple_status_got_mode, properties);
- mm_modem_gsm_network_get_registration_info (gsm, simple_status_got_reg_info, properties);
+ /* Start polling registration status and signal quality when enabled */
+
+ state = mm_modem_get_state (MM_MODEM (self));
+ if (state >= MM_MODEM_STATE_ENABLED) {
+ if (!priv->poll_id)
+ priv->poll_id = g_timeout_add_seconds (30, periodic_poll_cb, self);
+ } else {
+ if (priv->poll_id)
+ g_source_remove (priv->poll_id);
+ priv->poll_id = 0;
+ }
}
/*****************************************************************************/
@@ -2033,6 +3793,8 @@ modem_init (MMModem *modem_class)
modem_class->connect = connect;
modem_class->disconnect = disconnect;
modem_class->get_info = get_card_info;
+ modem_class->get_supported_charsets = get_supported_charsets;
+ modem_class->set_charset = set_charset;
}
static void
@@ -2040,10 +3802,12 @@ modem_gsm_card_init (MMModemGsmCard *class)
{
class->get_imei = get_imei;
class->get_imsi = get_imsi;
+ class->get_operator_id = get_operator_id;
class->send_pin = send_pin;
class->send_puk = send_puk;
class->enable_pin = enable_pin;
class->change_pin = change_pin;
+ class->get_unlock_retries = get_unlock_retries;
}
static void
@@ -2051,6 +3815,7 @@ modem_gsm_network_init (MMModemGsmNetwork *class)
{
class->do_register = do_register;
class->get_registration_info = get_registration_info;
+ class->set_allowed_mode = set_allowed_mode;
class->set_apn = set_apn;
class->scan = scan;
class->get_signal_quality = get_signal_quality;
@@ -2072,6 +3837,22 @@ modem_simple_init (MMModemSimple *class)
static void
mm_generic_gsm_init (MMGenericGsm *self)
{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ priv->act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN;
+ priv->reg_regex = mm_gsm_creg_regex_get (TRUE);
+ priv->roam_allowed = TRUE;
+
+ mm_properties_changed_signal_register_property (G_OBJECT (self),
+ MM_MODEM_GSM_NETWORK_ALLOWED_MODE,
+ MM_MODEM_GSM_NETWORK_DBUS_INTERFACE);
+
+ mm_properties_changed_signal_register_property (G_OBJECT (self),
+ MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY,
+ MM_MODEM_GSM_NETWORK_DBUS_INTERFACE);
+
+ g_signal_connect (self, "notify::" MM_MODEM_STATE,
+ G_CALLBACK (modem_state_changed), NULL);
}
static void
@@ -2086,6 +3867,8 @@ set_property (GObject *object, guint prop_id,
case MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL:
case MM_GENERIC_GSM_PROP_SUPPORTED_BANDS:
case MM_GENERIC_GSM_PROP_SUPPORTED_MODES:
+ case MM_GENERIC_GSM_PROP_ALLOWED_MODE:
+ case MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY:
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -2123,7 +3906,7 @@ get_property (GObject *object, guint prop_id,
g_value_set_string (value, "");
break;
case MM_GENERIC_GSM_PROP_INIT_CMD:
- g_value_set_string (value, "Z E0 V1 +CMEE=1");
+ g_value_set_string (value, "Z E0 V1");
break;
case MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL:
g_value_set_string (value, "X4 &C1");
@@ -2134,6 +3917,15 @@ get_property (GObject *object, guint prop_id,
case MM_GENERIC_GSM_PROP_SUPPORTED_MODES:
g_value_set_uint (value, 0);
break;
+ case MM_GENERIC_GSM_PROP_ALLOWED_MODE:
+ g_value_set_uint (value, priv->allowed_mode);
+ break;
+ case MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY:
+ if (mm_modem_get_state (MM_MODEM (object)) >= MM_MODEM_STATE_ENABLED)
+ g_value_set_uint (value, priv->act);
+ else
+ g_value_set_uint (value, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -2147,6 +3939,23 @@ finalize (GObject *object)
mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (object));
+ if (priv->pin_check_timeout) {
+ g_source_remove (priv->pin_check_timeout);
+ priv->pin_check_timeout = 0;
+ }
+
+ if (priv->poll_id) {
+ g_source_remove (priv->poll_id);
+ priv->poll_id = 0;
+ }
+
+ if (priv->signal_quality_id) {
+ g_source_remove (priv->signal_quality_id);
+ priv->signal_quality_id = 0;
+ }
+
+ mm_gsm_creg_regex_destroy (priv->reg_regex);
+
g_free (priv->oper_code);
g_free (priv->oper_name);
@@ -2167,6 +3976,8 @@ mm_generic_gsm_class_init (MMGenericGsmClass *klass)
object_class->finalize = finalize;
klass->do_enable = real_do_enable;
+ klass->do_enable_power_up_done = real_do_enable_power_up_done;
+ klass->do_disconnect = real_do_disconnect;
/* Properties */
g_object_class_override_property (object_class,
@@ -2185,6 +3996,14 @@ mm_generic_gsm_class_init (MMGenericGsmClass *klass)
MM_GENERIC_GSM_PROP_SUPPORTED_MODES,
MM_MODEM_GSM_CARD_SUPPORTED_MODES);
+ g_object_class_override_property (object_class,
+ MM_GENERIC_GSM_PROP_ALLOWED_MODE,
+ MM_MODEM_GSM_NETWORK_ALLOWED_MODE);
+
+ g_object_class_override_property (object_class,
+ MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY,
+ MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY);
+
g_object_class_install_property
(object_class, MM_GENERIC_GSM_PROP_POWER_UP_CMD,
g_param_spec_string (MM_GENERIC_GSM_POWER_UP_CMD,