diff options
Diffstat (limited to 'src/mm-generic-gsm.c')
-rw-r--r-- | src/mm-generic-gsm.c | 1310 |
1 files changed, 1175 insertions, 135 deletions
diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index 08cde10..98713b0 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -25,6 +25,7 @@ #include "mm-modem-gsm-card.h" #include "mm-modem-gsm-network.h" #include "mm-modem-gsm-sms.h" +#include "mm-modem-gsm-ussd.h" #include "mm-modem-simple.h" #include "mm-errors.h" #include "mm-callback-info.h" @@ -32,21 +33,26 @@ #include "mm-qcdm-serial-port.h" #include "mm-serial-parsers.h" #include "mm-modem-helpers.h" -#include "mm-options.h" +#include "mm-log.h" #include "mm-properties-changed-signal.h" #include "mm-utils.h" +#include "mm-modem-location.h" static void modem_init (MMModem *modem_class); static void modem_gsm_card_init (MMModemGsmCard *gsm_card_class); static void modem_gsm_network_init (MMModemGsmNetwork *gsm_network_class); static void modem_gsm_sms_init (MMModemGsmSms *gsm_sms_class); +static void modem_gsm_ussd_init (MMModemGsmUssd *gsm_ussd_class); static void modem_simple_init (MMModemSimple *class); +static void modem_location_init (MMModemLocation *class); G_DEFINE_TYPE_EXTENDED (MMGenericGsm, mm_generic_gsm, MM_TYPE_MODEM_BASE, 0, G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init) G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_CARD, modem_gsm_card_init) G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_NETWORK, modem_gsm_network_init) G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_SMS, modem_gsm_sms_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_LOCATION, modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_USSD, modem_gsm_ussd_init) G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_SIMPLE, modem_simple_init)) #define MM_GENERIC_GSM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_GENERIC_GSM, MMGenericGsmPrivate)) @@ -60,6 +66,9 @@ typedef struct { gboolean pin_checked; guint32 pin_check_tries; guint pin_check_timeout; + char *simid; + gboolean simid_checked; + guint32 simid_tries; MMModemGsmAllowedMode allowed_mode; @@ -87,8 +96,14 @@ typedef struct { MMCallbackInfo *pending_reg_info; gboolean manual_reg; + gboolean cmer_enabled; + guint roam_ind; + guint signal_ind; + guint service_ind; + guint signal_quality_id; - time_t signal_quality_timestamp; + time_t signal_emit_timestamp; + time_t signal_update_timestamp; guint32 signal_quality; gint cid; @@ -99,6 +114,13 @@ typedef struct { MMAtSerialPort *secondary; MMQcdmSerialPort *qcdm; MMPort *data; + + /* Location API */ + guint32 loc_caps; + gboolean loc_enabled; + gboolean loc_signal; + + MMModemGsmUssdState ussd_state; } MMGenericGsmPrivate; static void get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info); @@ -139,10 +161,18 @@ static void reg_info_updated (MMGenericGsm *self, gboolean update_name, const char *oper_name); +static void update_lac_ci (MMGenericGsm *self, gulong lac, gulong ci, guint idx); + +static void ciev_received (MMAtSerialPort *port, + GMatchInfo *info, + gpointer user_data); + MMModem * mm_generic_gsm_new (const char *device, const char *driver, - const char *plugin) + const char *plugin, + guint vendor, + guint product) { g_return_val_if_fail (device != NULL, NULL); g_return_val_if_fail (driver != NULL, NULL); @@ -152,6 +182,8 @@ mm_generic_gsm_new (const char *device, MM_MODEM_MASTER_DEVICE, device, MM_MODEM_DRIVER, driver, MM_MODEM_PLUGIN, plugin, + MM_MODEM_HW_VID, vendor, + MM_MODEM_HW_PID, product, NULL)); } @@ -235,6 +267,10 @@ pin_check_done (MMAtSerialPort *port, else if (response && strstr (response->str, "+CPIN: ")) { const char *str = strstr (response->str, "+CPIN: ") + 7; + /* Some phones (Motorola EZX models) seem to quote the response */ + if (str[0] == '"') + str++; + 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) @@ -306,12 +342,26 @@ get_imei_cb (MMModem *modem, } } +static void +get_info_cb (MMModem *modem, + const char *manufacturer, + const char *model, + const char *version, + GError *error, + gpointer user_data) +{ + /* Base class handles saving the info for us */ + if (modem) + mm_serial_port_close (MM_SERIAL_PORT (MM_GENERIC_GSM_GET_PRIVATE (modem)->primary)); +} + /*****************************************************************************/ static MMModemGsmNetworkRegStatus -gsm_reg_status (MMGenericGsm *self) +gsm_reg_status (MMGenericGsm *self, guint32 *out_idx) { MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + guint32 idx = 1; /* Some devices (Blackberries for example) will respond to +CGREG, but * return ERROR for +CREG, probably because their firmware is just stupid. @@ -320,23 +370,36 @@ gsm_reg_status (MMGenericGsm *self) */ 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]; + || priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) { + idx = 0; + goto out; + } 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]; + || priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) { + idx = 1; + goto out; + } - if (priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING) - return priv->reg_status[0]; + if (priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING) { + idx = 0; + goto out; + } - if (priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING) - return priv->reg_status[1]; + if (priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING) { + idx = 1; + goto out; + } - if (priv->reg_status[0] != MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN) - return priv->reg_status[0]; + if (priv->reg_status[0] != MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN) { + idx = 0; + goto out; + } - return priv->reg_status[1]; +out: + if (out_idx) + *out_idx = idx; + return priv->reg_status[idx]; } void @@ -350,7 +413,7 @@ mm_generic_gsm_update_enabled_state (MMGenericGsm *self, if (stay_connected && (mm_modem_get_state (MM_MODEM (self)) >= MM_MODEM_STATE_DISCONNECTING)) return; - switch (gsm_reg_status (self)) { + switch (gsm_reg_status (self, NULL)) { 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); @@ -373,13 +436,211 @@ check_valid (MMGenericGsm *self) MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); gboolean new_valid = FALSE; - if (priv->primary && priv->data && priv->pin_checked) + if (priv->primary && priv->data && priv->pin_checked && priv->simid_checked) new_valid = TRUE; mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid); } +static void +get_iccid_done (MMModem *modem, + const char *response, + GError *error, + gpointer user_data) +{ + MMGenericGsmPrivate *priv; + const char *p = response; + GChecksum *sum = NULL; + + if (error || !response || !strlen (response)) + goto done; + + sum = g_checksum_new (G_CHECKSUM_SHA1); + + /* Make sure it looks like an ICCID */ + while (*p) { + if (!isdigit (*p)) { + g_warning ("%s: invalid ICCID format (not a digit)", __func__); + goto done; + } + g_checksum_update (sum, (const guchar *) p++, 1); + } + + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + g_free (priv->simid); + priv->simid = g_strdup (g_checksum_get_string (sum)); + + mm_dbg ("SIM ID source '%s'", response); + mm_dbg ("SIM ID '%s'", priv->simid); + + g_object_notify (G_OBJECT (modem), MM_MODEM_GSM_CARD_SIM_IDENTIFIER); + +done: + if (sum) + g_checksum_free (sum); + + if (modem) { + MM_GENERIC_GSM_GET_PRIVATE (modem)->simid_checked = TRUE; + check_valid (MM_GENERIC_GSM (modem)); + } +} + +#define ICCID_CMD "+CRSM=176,12258,0,0,10" + +static void +real_get_iccid_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *str; + int sw1, sw2; + gboolean success = FALSE; + char buf[21], swapped[21]; + + if (error) { + info->error = g_error_copy (error); + goto done; + } + + memset (buf, 0, sizeof (buf)); + str = mm_strip_tag (response->str, "+CRSM:"); + if (sscanf (str, "%d,%d,\"%20c\"", &sw1, &sw2, (char *) &buf) == 3) + success = TRUE; + else { + /* May not include quotes... */ + if (sscanf (str, "%d,%d,%20c", &sw1, &sw2, (char *) &buf) == 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 len = 0; + int f_pos = -1, i; + + /* Make sure the buffer is only digits or 'F' */ + for (len = 0; len < sizeof (buf) && buf[len]; len++) { + if (isdigit (buf[len])) + continue; + if (buf[len] == 'F' || buf[len] == 'f') { + buf[len] = 'F'; /* canonicalize the F */ + f_pos = len; + continue; + } + if (buf[len] == '\"') { + buf[len] = 0; + break; + } + + /* Invalid character */ + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "CRSM ICCID response contained invalid character '%c'", + buf[len]); + goto done; + } + + /* BCD encoded ICCIDs are 20 digits long */ + if (len != 20) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Invalid +CRSM ICCID response size (was %zd, expected 20)", + len); + goto done; + } + + /* Ensure if there's an 'F' that it's second-to-last */ + if ((f_pos >= 0) && (f_pos != len - 2)) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Invalid +CRSM ICCID length (unexpected F)"); + goto done; + } + + /* Swap digits in the EFiccid response to get the actual ICCID, each + * group of 2 digits is reversed in the +CRSM response. i.e.: + * + * 21436587 -> 12345678 + */ + memset (swapped, 0, sizeof (swapped)); + for (i = 0; i < 10; i++) { + swapped[i * 2] = buf[(i * 2) + 1]; + swapped[(i * 2) + 1] = buf[i * 2]; + } + + /* Zero out the F for 19 digit ICCIDs */ + if (swapped[len - 1] == 'F') + swapped[len - 1] = 0; + + mm_callback_info_set_result (info, g_strdup (swapped), g_free); + } else { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + if (priv->simid_tries++ < 2) { + /* Try one more time... Gobi 1K cards may reply to the first + * request with '+CRSM: 106,134,""' which is bogus because + * subsequent requests work fine. + */ + mm_at_serial_port_queue_command (port, ICCID_CMD, 20, real_get_iccid_done, info); + return; + } 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: + /* Balance open from real_get_sim_iccid() */ + mm_serial_port_close (MM_SERIAL_PORT (port)); + + mm_callback_info_schedule (info); +} + +static void +real_get_sim_iccid (MMGenericGsm *self, + MMModemStringFn callback, + gpointer callback_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + GError *error = NULL; + + port = mm_generic_gsm_get_best_at_port (self, &error); + if (!port) { + callback (MM_MODEM (self), NULL, error, callback_data); + g_clear_error (&error); + return; + } + + if (!mm_serial_port_open (MM_SERIAL_PORT (port), &error)) { + callback (MM_MODEM (self), NULL, error, callback_data); + g_clear_error (&error); + return; + } + + info = mm_callback_info_string_new (MM_MODEM (self), callback, callback_data); + + /* READ BINARY of EFiccid (ICC Identification) ETSI TS 102.221 section 13.2 */ + mm_at_serial_port_queue_command (port, ICCID_CMD, 20, real_get_iccid_done, info); +} + +static void +initial_iccid_check (MMGenericGsm *self) +{ + g_assert (MM_GENERIC_GSM_GET_CLASS (self)->get_sim_iccid); + MM_GENERIC_GSM_GET_CLASS (self)->get_sim_iccid (self, get_iccid_done, NULL); +} + static void initial_pin_check_done (MMModem *modem, GError *error, gpointer user_data); static gboolean @@ -415,9 +676,13 @@ initial_pin_check_done (MMModem *modem, GError *error, gpointer user_data) g_source_remove (priv->pin_check_timeout); priv->pin_check_timeout = g_timeout_add_seconds (2, pin_check_again, modem); } else { + /* Try to get the SIM ICCID after we've checked PIN status and the SIM + * is ready. + */ + initial_iccid_check (MM_GENERIC_GSM (modem)); + priv->pin_checked = TRUE; mm_serial_port_close (MM_SERIAL_PORT (priv->primary)); - check_valid (MM_GENERIC_GSM (modem)); } } @@ -476,6 +741,34 @@ initial_imei_check (MMGenericGsm *self) } } +static void +initial_info_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); + mm_modem_base_get_card_info (MM_MODEM_BASE (self), + priv->primary, + NULL, + get_info_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) { @@ -519,6 +812,10 @@ mm_generic_gsm_grab_port (MMGenericGsm *self, } mm_gsm_creg_regex_destroy (array); + regex = g_regex_new ("\\r\\n\\+CIEV: (\\d+),(\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, ciev_received, self, NULL); + g_regex_unref (regex); + if (ptype == MM_PORT_TYPE_PRIMARY) { priv->primary = MM_AT_SERIAL_PORT (port); if (!priv->data) { @@ -526,12 +823,17 @@ mm_generic_gsm_grab_port (MMGenericGsm *self, g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); } - /* Get modem's initial lock/unlock state */ - initial_pin_check (self); + /* Get the modem's general info */ + initial_info_check (self); - /* Get modem's IMEI number */ + /* Get modem's IMEI */ initial_imei_check (self); + /* Get modem's initial lock/unlock state; this also ensures the + * SIM is ready by waiting if necessary for the SIM to initalize. + */ + initial_pin_check (self); + } else if (ptype == MM_PORT_TYPE_SECONDARY) priv->secondary = MM_AT_SERIAL_PORT (port); } else if (MM_IS_QCDM_SERIAL_PORT (port)) { @@ -615,6 +917,18 @@ release_port (MMModem *modem, const char *subsys, const char *name) } static void +add_loc_capability (MMGenericGsm *self, guint32 cap) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + guint32 old_caps = priv->loc_caps; + + priv->loc_caps |= cap; + if (priv->loc_caps != old_caps) { + g_object_notify (G_OBJECT (self), MM_MODEM_LOCATION_CAPABILITIES); + } +} + +static void reg_poll_response (MMAtSerialPort *port, GString *response, GError *error, @@ -661,9 +975,12 @@ periodic_poll_cb (gpointer user_data) 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); + /* Don't poll signal quality if we got a notification in the past 10 seconds */ + if (time (NULL) - priv->signal_update_timestamp > 10) { + 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); @@ -671,6 +988,57 @@ periodic_poll_cb (gpointer user_data) return TRUE; /* continue running */ } +#define CREG_NUM_TAG "creg-num" +#define CGREG_NUM_TAG "cgreg-num" + +static void +initial_unsolicited_reg_check_done (MMCallbackInfo *info) +{ + MMGenericGsmPrivate *priv; + guint creg_num, cgreg_num; + + if (!info->modem || info->error) + goto done; + + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + if (!priv->secondary) + goto done; + + /* Enable unsolicited registration responses on secondary ports too, + * to ensure that we get the response even if the modem is connected + * on the primary port. We enable responses on both ports because we + * cannot trust modems to reliably send the responses on the port we + * enable them on. + */ + + creg_num = GPOINTER_TO_UINT (mm_callback_info_get_data (info, CREG_NUM_TAG)); + switch (creg_num) { + case 1: + mm_at_serial_port_queue_command (priv->secondary, "+CREG=1", 3, NULL, NULL); + break; + case 2: + mm_at_serial_port_queue_command (priv->secondary, "+CREG=2", 3, NULL, NULL); + break; + default: + break; + } + + cgreg_num = GPOINTER_TO_UINT (mm_callback_info_get_data (info, CGREG_NUM_TAG)); + switch (cgreg_num) { + case 1: + mm_at_serial_port_queue_command (priv->secondary, "+CGREG=1", 3, NULL, NULL); + break; + case 2: + mm_at_serial_port_queue_command (priv->secondary, "+CGREG=2", 3, NULL, NULL); + break; + default: + break; + } + +done: + mm_callback_info_schedule (info); +} + static void cgreg1_done (MMAtSerialPort *port, GString *response, @@ -688,11 +1056,14 @@ cgreg1_done (MMAtSerialPort *port, /* The modem doesn't like unsolicited CGREG, so we'll need to poll */ priv->cgreg_poll = TRUE; - } + } else + mm_callback_info_set_data (info, CGREG_NUM_TAG, GUINT_TO_POINTER (1), NULL); + /* Success; get initial state */ mm_at_serial_port_queue_command (port, "+CGREG?", 10, reg_poll_response, info->modem); } - mm_callback_info_schedule (info); + + initial_unsolicited_reg_check_done (info); } static void @@ -711,11 +1082,15 @@ cgreg2_done (MMAtSerialPort *port, /* Try CGREG=1 instead */ mm_at_serial_port_queue_command (port, "+CGREG=1", 3, cgreg1_done, info); } else { + add_loc_capability (MM_GENERIC_GSM (info->modem), MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI); + + mm_callback_info_set_data (info, CGREG_NUM_TAG, GUINT_TO_POINTER (2), NULL); + /* 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); + initial_unsolicited_reg_check_done (info); } } else { /* Modem got removed */ @@ -740,7 +1115,9 @@ creg1_done (MMAtSerialPort *port, /* The modem doesn't like unsolicited CREG, so we'll need to poll */ priv->creg_poll = TRUE; - } + } else + mm_callback_info_set_data (info, CREG_NUM_TAG, GUINT_TO_POINTER (1), NULL); + /* Success; get initial state */ mm_at_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, info->modem); @@ -767,6 +1144,10 @@ creg2_done (MMAtSerialPort *port, g_clear_error (&info->error); mm_at_serial_port_queue_command (port, "+CREG=1", 3, creg1_done, info); } else { + add_loc_capability (MM_GENERIC_GSM (info->modem), MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI); + + mm_callback_info_set_data (info, CREG_NUM_TAG, GUINT_TO_POINTER (2), NULL); + /* Success; get initial state */ mm_at_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, info->modem); @@ -807,6 +1188,7 @@ static guint32 best_charsets[] = { MM_MODEM_CHARSET_UCS2, MM_MODEM_CHARSET_8859_1, MM_MODEM_CHARSET_IRA, + MM_MODEM_CHARSET_GSM, MM_MODEM_CHARSET_UNKNOWN }; @@ -865,7 +1247,7 @@ supported_charsets_done (MMModem *modem, } /* 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); + mm_modem_set_charset (modem, best_charsets[0], enabled_set_charset_done, info); } static void @@ -881,14 +1263,87 @@ get_allowed_mode_done (MMModem *modem, } static void -get_enable_info_done (MMModem *modem, - const char *manufacturer, - const char *model, - const char *version, - GError *error, - gpointer user_data) +ciev_received (MMAtSerialPort *port, + GMatchInfo *info, + gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + int quality = 0, ind = 0; + char *str; + + if (!priv->cmer_enabled) + return; + + str = g_match_info_fetch (info, 1); + if (str) + ind = atoi (str); + g_free (str); + + if (ind == priv->signal_ind) { + str = g_match_info_fetch (info, 2); + if (str) { + quality = atoi (str); + mm_generic_gsm_update_signal_quality (self, quality * 20); + } + g_free (str); + } + + /* FIXME: handle roaming and service indicators */ +} + +static void +cmer_cb (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - /* Modem base class handles the response for us */ + if (!error) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (user_data); + + priv->cmer_enabled = TRUE; + + /* Enable CMER on the secondary port if we can too */ + if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) + mm_at_serial_port_queue_command (priv->secondary, "+CMER=3,0,0,1", 3, NULL, NULL); + } +} + +static void +cind_cb (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMGenericGsm *self; + MMGenericGsmPrivate *priv; + GHashTable *indicators; + + if (error) + return; + + self = MM_GENERIC_GSM (user_data); + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + indicators = mm_parse_cind_test_response (response->str, NULL); + if (indicators) { + CindResponse *r; + + r = g_hash_table_lookup (indicators, "signal"); + if (r) + priv->signal_ind = cind_response_get_index (r); + + r = g_hash_table_lookup (indicators, "roam"); + if (r) + priv->roam_ind = cind_response_get_index (r); + + r = g_hash_table_lookup (indicators, "service"); + if (r) + priv->service_ind = cind_response_get_index (r); + + mm_at_serial_port_queue_command (port, "+CMER=3,0,0,1", 3, cmer_cb, self); + g_hash_table_destroy (indicators); + } } void @@ -916,20 +1371,20 @@ mm_generic_gsm_enable_complete (MMGenericGsm *self, */ 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_dbg ("error opening secondary port: (%d) %s", + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); } } /* 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); + mm_at_serial_port_queue_command (priv->primary, "+CIND=?", 3, cind_cb, self); + + /* Try one more time to get the SIM ID */ + if (!priv->simid) + MM_GENERIC_GSM_GET_CLASS (self)->get_sim_iccid (self, get_iccid_done, NULL); /* Get allowed mode */ if (MM_GENERIC_GSM_GET_CLASS (self)->get_allowed_mode) @@ -1109,6 +1564,7 @@ disable_flash_done (MMSerialPort *port, GError *error, gpointer user_data) { + MMGenericGsmPrivate *priv; MMCallbackInfo *info = user_data; MMModemState prev_state; char *cmd = NULL; @@ -1127,9 +1583,21 @@ disable_flash_done (MMSerialPort *port, return; } + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + /* 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); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CREG=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CGREG=0", 3, NULL, NULL); + + if (priv->cmer_enabled) { + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CMER=0", 3, NULL, NULL); + + /* And on the secondary port */ + if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) + mm_at_serial_port_queue_command (priv->secondary, "+CMER=0", 3, NULL, NULL); + + priv->cmer_enabled = FALSE; + } g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_DOWN_CMD, &cmd, NULL); if (cmd && strlen (cmd)) @@ -1140,6 +1608,15 @@ disable_flash_done (MMSerialPort *port, } static void +secondary_unsolicited_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + mm_serial_port_close_force (MM_SERIAL_PORT (port)); +} + +static void disable (MMModem *modem, MMModemFn callback, gpointer user_data) @@ -1170,15 +1647,16 @@ disable (MMModem *modem, priv->pin_check_timeout = 0; } - priv->lac[0] = 0; - priv->lac[1] = 0; - priv->cell_id[0] = 0; - priv->cell_id[1] = 0; + update_lac_ci (self, 0, 0, 0); + update_lac_ci (self, 0, 0, 1); _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)); + /* Clean up the secondary port if it's open */ + if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) { + mm_at_serial_port_queue_command (priv->secondary, "+CREG=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (priv->secondary, "+CGREG=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (priv->secondary, "+CMER=0", 3, secondary_unsolicited_done, NULL); + } info = mm_callback_info_new (modem, callback, user_data); @@ -1370,6 +1848,7 @@ get_card_info (MMModem *modem, } #define PIN_PORT_TAG "pin-port" +#define SAVED_ERROR_TAG "error" static void pin_puk_recheck_done (MMModem *modem, GError *error, gpointer user_data); @@ -1389,6 +1868,7 @@ pin_puk_recheck_done (MMModem *modem, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; MMSerialPort *port; + GError *saved_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 @@ -1429,6 +1909,13 @@ pin_puk_recheck_done (MMModem *modem, GError *error, gpointer user_data) if (modem && port) mm_serial_port_close (port); + /* If we have a saved error from sending PIN/PUK, return that to callers */ + saved_error = mm_callback_info_get_data (info, SAVED_ERROR_TAG); + if (saved_error) { + g_clear_error (&info->error); + info->error = saved_error; + } + mm_callback_info_schedule (info); } @@ -1441,10 +1928,18 @@ send_puk_done (MMAtSerialPort *port, MMCallbackInfo *info = (MMCallbackInfo *) user_data; if (error) { - info->error = g_error_copy (error); - mm_callback_info_schedule (info); - mm_serial_port_close (MM_SERIAL_PORT (port)); - return; + if (error->domain != MM_MOBILE_ERROR) { + info->error = g_error_copy (error); + mm_callback_info_schedule (info); + mm_serial_port_close (MM_SERIAL_PORT (port)); + return; + } else { + /* Keep the real error around so we can send it back + * when we're done rechecking CPIN status. + */ + mm_callback_info_set_data (info, SAVED_ERROR_TAG, + g_error_copy (error), NULL); + } } /* Get latest PIN status */ @@ -1496,10 +1991,18 @@ send_pin_done (MMAtSerialPort *port, MMCallbackInfo *info = (MMCallbackInfo *) user_data; if (error) { - info->error = g_error_copy (error); - mm_callback_info_schedule (info); - mm_serial_port_close (MM_SERIAL_PORT (port)); - return; + if (error->domain != MM_MOBILE_ERROR) { + info->error = g_error_copy (error); + mm_callback_info_schedule (info); + mm_serial_port_close (MM_SERIAL_PORT (port)); + return; + } else { + /* Keep the real error around so we can send it back + * when we're done rechecking CPIN status. + */ + mm_callback_info_set_data (info, SAVED_ERROR_TAG, + g_error_copy (error), NULL); + } } /* Get latest PIN status */ @@ -1634,9 +2137,9 @@ reg_info_updated (MMGenericGsm *self, 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); + old_status = gsm_reg_status (self, NULL); priv->reg_status[rs_type - 1] = status; - if (gsm_reg_status (self) != old_status) + if (gsm_reg_status (self, NULL) != old_status) changed = TRUE; } @@ -1658,7 +2161,7 @@ reg_info_updated (MMGenericGsm *self, if (changed) { mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (self), - gsm_reg_status (self), + gsm_reg_status (self, NULL), priv->oper_code, priv->oper_name); } @@ -1716,12 +2219,22 @@ parse_operator (const char *reply, MMModemCharset cur_charset) 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); + if (operator) { + /* 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 (cur_charset == MM_MODEM_CHARSET_UCS2) + convert_operator_from_ucs2 (&operator); + + /* Ensure the operator name is valid UTF-8 so that we can send it + * through D-Bus and such. + */ + if (!g_utf8_validate (operator, -1, NULL)) { + g_free (operator); + operator = NULL; + } + } return operator; } @@ -1803,7 +2316,7 @@ roam_disconnect_done (MMModem *modem, GError *error, gpointer user_data) { - g_message ("Disconnected because roaming is not allowed"); + mm_info ("Disconnected because roaming is not allowed"); } static void @@ -1834,9 +2347,9 @@ mm_generic_gsm_set_reg_status (MMGenericGsm *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); + mm_dbg ("%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); @@ -1948,18 +2461,15 @@ reg_state_changed (MMAtSerialPort *port, { MMGenericGsm *self = MM_GENERIC_GSM (user_data); MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); - guint32 state = 0, idx; + guint32 state = 0; gulong lac = 0, cell_id = 0; gint act = -1; gboolean cgreg = FALSE; GError *error = NULL; 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)"); - } + mm_warn ("error parsing unsolicited registration: %s", + error && error->message ? error->message : "(unknown)"); return; } @@ -1974,9 +2484,7 @@ reg_state_changed (MMAtSerialPort *port, } } - idx = cgreg ? 1 : 0; - priv->lac[idx] = lac; - priv->cell_id[idx] = cell_id; + update_lac_ci (self, lac, cell_id, cgreg ? 1 : 0); /* Only update access technology if it appeared in the CREG/CGREG response */ if (act != -1) @@ -2014,7 +2522,7 @@ handle_reg_status_response (MMGenericGsm *self, { MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); GMatchInfo *match_info; - guint32 status = 0, idx; + guint32 status = 0; gulong lac = 0, ci = 0; gint act = -1; gboolean cgreg = FALSE; @@ -2043,9 +2551,7 @@ handle_reg_status_response (MMGenericGsm *self, } /* Success; update cached location information */ - idx = cgreg ? 1 : 0; - priv->lac[idx] = lac; - priv->cell_id[idx] = ci; + update_lac_ci (self, lac, ci, cgreg ? 1 : 0); /* Only update access technology if it appeared in the CREG/CGREG response */ if (act != -1) @@ -2108,7 +2614,7 @@ get_reg_status_done (MMAtSerialPort *port, goto reg_done; } - status = gsm_reg_status (self); + status = gsm_reg_status (self, NULL); 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) { @@ -2222,7 +2728,7 @@ do_register (MMModemGsmNetwork *modem, if (network_id) { command = g_strdup_printf ("+COPS=1,2,\"%s\"", network_id); priv->manual_reg = TRUE; - } else if (reg_is_idle (gsm_reg_status (self)) || priv->manual_reg) { + } else if (reg_is_idle (gsm_reg_status (self, NULL)) || priv->manual_reg) { command = g_strdup ("+COPS=0,,"); priv->manual_reg = FALSE; } @@ -2260,7 +2766,7 @@ gsm_network_reg_info_invoke (MMCallbackInfo *info) MMModemGsmNetworkRegInfoFn callback = (MMModemGsmNetworkRegInfoFn) info->callback; callback (MM_MODEM_GSM_NETWORK (info->modem), - gsm_reg_status (MM_GENERIC_GSM (info->modem)), + gsm_reg_status (MM_GENERIC_GSM (info->modem), NULL), priv->oper_code, priv->oper_name, info->error, @@ -2369,7 +2875,7 @@ connect (MMModem *modem, MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); MMCallbackInfo *info; char *command; - guint32 cid = mm_generic_gsm_get_cid (MM_GENERIC_GSM (modem)); + gint cid = mm_generic_gsm_get_cid (MM_GENERIC_GSM (modem)); info = mm_callback_info_new (modem, callback, user_data); @@ -2803,7 +3309,7 @@ emit_signal_quality_change (gpointer user_data) MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); priv->signal_quality_id = 0; - priv->signal_quality_timestamp = time (NULL); + priv->signal_emit_timestamp = time (NULL); mm_modem_gsm_network_signal_quality (MM_MODEM_GSM_NETWORK (self), priv->signal_quality); return FALSE; } @@ -2820,6 +3326,8 @@ mm_generic_gsm_update_signal_quality (MMGenericGsm *self, guint32 quality) priv = MM_GENERIC_GSM_GET_PRIVATE (self); + priv->signal_update_timestamp = time (NULL); + if (priv->signal_quality == quality) return; @@ -2831,12 +3339,12 @@ mm_generic_gsm_update_signal_quality (MMGenericGsm *self, guint32 quality) * haven't been any updates in a while. */ if (!priv->signal_quality_id) { - if (priv->signal_quality_timestamp > 0) { + if (priv->signal_emit_timestamp > 0) { time_t curtime; long int diff; curtime = time (NULL); - diff = curtime - priv->signal_quality_timestamp; + diff = curtime - priv->signal_emit_timestamp; if (diff == 0) { /* If the device is sending more than one update per second, * make sure we don't spam clients with signals. @@ -2861,11 +3369,43 @@ mm_generic_gsm_update_signal_quality (MMGenericGsm *self, guint32 quality) } } +#define CIND_TAG "+CIND:" + static void -get_signal_quality_done (MMAtSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +get_cind_signal_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + GByteArray *indicators; + guint quality; + + info->error = mm_modem_check_removed (info->modem, error); + if (!info->error) { + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + indicators = mm_parse_cind_query_response (response->str, &info->error); + if (indicators) { + if (indicators->len >= priv->signal_ind) { + quality = g_array_index (indicators, guint8, priv->signal_ind); + quality = CLAMP (quality, 0, 5) * 20; + mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (info->modem), quality); + mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL); + } + g_byte_array_free (indicators, TRUE); + } + } + + mm_callback_info_schedule (info); +} + +static void +get_csq_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; char *reply = response->str; @@ -2918,9 +3458,13 @@ get_signal_quality (MMModemGsmNetwork *modem, 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 { + if (port) { + /* Prefer +CIND if the modem supports it, fall back to +CSQ otherwise */ + if (priv->signal_ind) + mm_at_serial_port_queue_command (port, "+CIND?", 3, get_cind_signal_done, info); + else + mm_at_serial_port_queue_command (port, "+CSQ", 3, get_csq_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); @@ -3282,7 +3826,7 @@ sms_send_done (MMAtSerialPort *port, } static void -sms_send (MMModemGsmSms *modem, +sms_send (MMModemGsmSms *modem, const char *number, const char *text, const char *smsc, @@ -3348,6 +3892,219 @@ mm_generic_gsm_get_best_at_port (MMGenericGsm *self, GError **error) } /*****************************************************************************/ +/* MMModemGsmUssd interface */ + +static void +ussd_update_state (MMGenericGsm *self, MMModemGsmUssdState new_state) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (new_state != priv->ussd_state) { + priv->ussd_state = new_state; + g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_STATE); + } +} + +static void +ussd_send_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + gint status; + gboolean parsed = FALSE; + MMModemGsmUssdState ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE; + const char *str, *start = NULL, *end = NULL; + char *reply = NULL, *converted; + + if (error) { + info->error = g_error_copy (error); + goto done; + } + + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + ussd_state = priv->ussd_state; + + str = mm_strip_tag (response->str, "+CUSD:"); + if (!str || !isdigit (*str)) + goto done; + + status = g_ascii_digit_value (*str); + switch (status) { + case 0: /* no further action required */ + ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE; + break; + case 1: /* further action required */ + ussd_state = MM_MODEM_GSM_USSD_STATE_USER_RESPONSE; + break; + case 2: + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "USSD terminated by network."); + ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE; + break; + case 4: + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Operiation not supported."); + ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE; + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Unknown USSD reply %d", status); + ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE; + break; + } + if (info->error) + goto done; + + /* look for the reply */ + if ((start = strchr (str, '"')) && (end = strrchr (str, '"')) && (start != end)) + reply = g_strndup (start + 1, end - start -1); + + if (reply) { + /* look for the reply data coding scheme */ + if ((start = strrchr (end, ',')) != NULL) + mm_dbg ("USSD data coding scheme %d", atoi (start + 1)); + + converted = mm_modem_charset_hex_to_utf8 (reply, priv->cur_charset); + mm_callback_info_set_result (info, converted, g_free); + parsed = TRUE; + g_free (reply); + } + +done: + if (!parsed && !info->error) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Could not parse USSD reply '%s'", + response->str); + } + mm_callback_info_schedule (info); + + if (info->modem) + ussd_update_state (MM_GENERIC_GSM (info->modem), ussd_state); +} + +static void +ussd_send (MMModemGsmUssd *modem, + const char *command, + MMModemStringFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + char *atc_command; + char *hex; + GByteArray *ussd_command = g_byte_array_new(); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMAtSerialPort *port; + + info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + /* encode to cur_charset */ + g_warn_if_fail (mm_modem_charset_byte_array_append (ussd_command, command, FALSE, priv->cur_charset)); + /* convert to hex representation */ + hex = utils_bin2hexstr (ussd_command->data, ussd_command->len); + g_byte_array_free (ussd_command, TRUE); + atc_command = g_strdup_printf ("+CUSD=1,\"%s\",15", hex); + g_free (hex); + + mm_at_serial_port_queue_command (port, atc_command, 10, ussd_send_done, info); + g_free (atc_command); + + ussd_update_state (MM_GENERIC_GSM (modem), MM_MODEM_GSM_USSD_STATE_ACTIVE); +} + +static void +ussd_initiate (MMModemGsmUssd *modem, + const char *command, + MMModemStringFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); + + if (priv->ussd_state != MM_MODEM_GSM_USSD_STATE_IDLE) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "USSD session already active."); + mm_callback_info_schedule (info); + return; + } + + ussd_send (modem, command, callback, user_data); + return; +} + +static void +ussd_respond (MMModemGsmUssd *modem, + const char *command, + MMModemStringFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); + + if (priv->ussd_state != MM_MODEM_GSM_USSD_STATE_USER_RESPONSE) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "No active USSD session, cannot respond."); + mm_callback_info_schedule (info); + return; + } + + ussd_send (modem, command, callback, user_data); + return; +} + +static void +ussd_cancel_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + + mm_callback_info_schedule (info); + + if (info->modem) + ussd_update_state (MM_GENERIC_GSM (info->modem), MM_MODEM_GSM_USSD_STATE_IDLE); +} + +static void +ussd_cancel (MMModemGsmUssd *modem, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + + info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "+CUSD=2", 10, ussd_cancel_done, info); +} + +/*****************************************************************************/ /* MMModemSimple interface */ typedef enum { @@ -3481,6 +4238,7 @@ simple_state_machine (MMModem *modem, GError *error, gpointer user_data) gboolean done = FALSE; MMModemGsmAllowedMode allowed_mode; gboolean home_only = FALSE; + char *data_device; info->error = mm_modem_check_removed (modem, error); if (info->error) @@ -3488,16 +4246,9 @@ simple_state_machine (MMModem *modem, GError *error, gpointer user_data) 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); - } + g_object_get (G_OBJECT (modem), MM_MODEM_DATA_DEVICE, &data_device, NULL); + mm_dbg ("(%s): simple connect state %d", data_device, state); + g_free (data_device); switch (state) { case SIMPLE_STATE_CHECK_PIN: @@ -3564,7 +4315,7 @@ simple_state_machine (MMModem *modem, GError *error, gpointer user_data) priv->roam_allowed = !home_only; /* Don't connect if we're not supposed to be roaming */ - status = gsm_reg_status (MM_GENERIC_GSM (modem)); + status = gsm_reg_status (MM_GENERIC_GSM (modem), NULL); 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, @@ -3596,29 +4347,21 @@ simple_connect (MMModemSimple *simple, gpointer user_data) { 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); + GHashTableIter iter; + gpointer key, value; + char *data_device; + + /* List simple connect properties when debugging */ + g_object_get (G_OBJECT (simple), MM_MODEM_DATA_DEVICE, &data_device, NULL); + 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); + mm_dbg ("(%s): %s => %s", 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", @@ -3761,6 +4504,159 @@ simple_get_status (MMModemSimple *simple, /*****************************************************************************/ +static gboolean +gsm_lac_ci_available (MMGenericGsm *self, guint32 *out_idx) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMModemGsmNetworkRegStatus status; + guint idx; + + /* Must be registered, and have operator code, LAC and CI before GSM_LAC_CI is valid */ + status = gsm_reg_status (self, &idx); + if (out_idx) + *out_idx = idx; + + if ( status != MM_MODEM_GSM_NETWORK_REG_STATUS_HOME + && status != MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) + return FALSE; + + if (!priv->oper_code || !strlen (priv->oper_code)) + return FALSE; + + if (!priv->lac[idx] || !priv->cell_id[idx]) + return FALSE; + + return TRUE; +} + +static void +update_lac_ci (MMGenericGsm *self, gulong lac, gulong ci, guint idx) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + gboolean changed = FALSE; + + if (lac != priv->lac[idx]) { + priv->lac[idx] = lac; + changed = TRUE; + } + + if (ci != priv->cell_id[idx]) { + priv->cell_id[idx] = ci; + changed = TRUE; + } + + if (changed && gsm_lac_ci_available (self, NULL) && priv->loc_enabled && priv->loc_signal) + g_object_notify (G_OBJECT (self), MM_MODEM_LOCATION_LOCATION); +} + +static void +destroy_gvalue (gpointer data) +{ + GValue *value = (GValue *) data; + + g_value_unset (value); + g_slice_free (GValue, value); +} + +static GHashTable * +make_location_hash (MMGenericGsm *self, GError **error) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + GHashTable *locations = NULL; + guint32 reg_idx = 0; + GValue *val; + char mcc[4] = { 0, 0, 0, 0 }; + char mnc[4] = { 0, 0, 0, 0 }; + + if (priv->loc_caps == MM_MODEM_LOCATION_CAPABILITY_UNKNOWN) { + g_set_error_literal (error, + MM_MODEM_ERROR, + MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Modem has no location capabilities"); + return NULL; + } + + locations = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, destroy_gvalue); + + if (!gsm_lac_ci_available (self, ®_idx)) + return locations; + + memcpy (mcc, priv->oper_code, 3); + /* Not all modems report 6-digit MNCs */ + memcpy (mnc, priv->oper_code + 3, 2); + if (strlen (priv->oper_code) == 6) + mnc[2] = priv->oper_code[5]; + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_STRING); + g_value_take_string (val, g_strdup_printf ("%s,%s,%lX,%lX", + mcc, + mnc, + priv->lac[reg_idx], + priv->cell_id[reg_idx])); + g_hash_table_insert (locations, + GUINT_TO_POINTER (MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI), + val); + + return locations; +} + +static void +location_enable (MMModemLocation *modem, + gboolean loc_enable, + gboolean signal_location, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMCallbackInfo *info; + + if (loc_enable != priv->loc_enabled) { + priv->loc_enabled = loc_enable; + g_object_notify (G_OBJECT (modem), MM_MODEM_LOCATION_ENABLED); + } + + if (signal_location != priv->loc_signal) { + priv->loc_signal = signal_location; + g_object_notify (G_OBJECT (modem), MM_MODEM_LOCATION_SIGNALS_LOCATION); + } + + if (loc_enable && signal_location && gsm_lac_ci_available (self, NULL)) + g_object_notify (G_OBJECT (modem), MM_MODEM_LOCATION_LOCATION); + + info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + mm_callback_info_schedule (info); +} + +static void +location_get (MMModemLocation *modem, + MMModemLocationGetFn callback, + gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + GHashTable *locations = NULL; + GError *error = NULL; + + if (priv->loc_caps == MM_MODEM_LOCATION_CAPABILITY_UNKNOWN) { + error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Modem has no location capabilities"); + } else if (priv->loc_enabled) + locations = make_location_hash (self, &error); + else + locations = g_hash_table_new (g_direct_hash, g_direct_equal); + + callback (modem, locations, error, user_data); + if (locations) + g_hash_table_destroy (locations); + g_clear_error (&error); +} + +/*****************************************************************************/ + static void modem_state_changed (MMGenericGsm *self, GParamSpec *pspec, gpointer user_data) { @@ -3798,6 +4694,13 @@ modem_init (MMModem *modem_class) } static void +modem_location_init (MMModemLocation *class) +{ + class->enable = location_enable; + class->get_location = location_get; +} + +static void modem_gsm_card_init (MMModemGsmCard *class) { class->get_imei = get_imei; @@ -3828,6 +4731,14 @@ modem_gsm_sms_init (MMModemGsmSms *class) } static void +modem_gsm_ussd_init (MMModemGsmUssd *class) +{ + class->initiate = ussd_initiate; + class->respond = ussd_respond; + class->cancel = ussd_cancel; +} + +static void modem_simple_init (MMModemSimple *class) { class->connect = simple_connect; @@ -3845,12 +4756,49 @@ mm_generic_gsm_init (MMGenericGsm *self) mm_properties_changed_signal_register_property (G_OBJECT (self), MM_MODEM_GSM_NETWORK_ALLOWED_MODE, + NULL, MM_MODEM_GSM_NETWORK_DBUS_INTERFACE); mm_properties_changed_signal_register_property (G_OBJECT (self), MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY, + NULL, MM_MODEM_GSM_NETWORK_DBUS_INTERFACE); + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_LOCATION_CAPABILITIES, + "Capabilities", + MM_MODEM_LOCATION_DBUS_INTERFACE); + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_LOCATION_ENABLED, + "Enabled", + MM_MODEM_LOCATION_DBUS_INTERFACE); + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_LOCATION_SIGNALS_LOCATION, + NULL, + MM_MODEM_LOCATION_DBUS_INTERFACE); + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_LOCATION_LOCATION, + NULL, + MM_MODEM_LOCATION_DBUS_INTERFACE); + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_GSM_USSD_STATE, + "State", + MM_MODEM_GSM_USSD_DBUS_INTERFACE); + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_GSM_USSD_NETWORK_NOTIFICATION, + "NetworkNotification", + MM_MODEM_GSM_USSD_DBUS_INTERFACE); + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_GSM_USSD_NETWORK_REQUEST, + "NetworkRequest", + MM_MODEM_GSM_USSD_DBUS_INTERFACE); + g_signal_connect (self, "notify::" MM_MODEM_STATE, G_CALLBACK (modem_state_changed), NULL); } @@ -3869,6 +4817,14 @@ set_property (GObject *object, guint prop_id, case MM_GENERIC_GSM_PROP_SUPPORTED_MODES: case MM_GENERIC_GSM_PROP_ALLOWED_MODE: case MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY: + case MM_GENERIC_GSM_PROP_SIM_IDENTIFIER: + case MM_GENERIC_GSM_PROP_LOC_CAPABILITIES: + case MM_GENERIC_GSM_PROP_LOC_ENABLED: + case MM_GENERIC_GSM_PROP_LOC_SIGNAL: + case MM_GENERIC_GSM_PROP_LOC_LOCATION: + case MM_GENERIC_GSM_PROP_USSD_STATE: + case MM_GENERIC_GSM_PROP_USSD_NETWORK_REQUEST: + case MM_GENERIC_GSM_PROP_USSD_NETWORK_NOTIFICATION: break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -3876,11 +4832,30 @@ set_property (GObject *object, guint prop_id, } } +static const char * +ussd_state_to_string (MMModemGsmUssdState ussd_state) +{ + switch (ussd_state) { + case MM_MODEM_GSM_USSD_STATE_IDLE: + return "idle"; + case MM_MODEM_GSM_USSD_STATE_ACTIVE: + return "active"; + case MM_MODEM_GSM_USSD_STATE_USER_RESPONSE: + return "user-response"; + default: + break; + } + + g_warning ("Unknown GSM USSD state %d", ussd_state); + return "unknown"; +} + static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (object); + GHashTable *locations = NULL; switch (prop_id) { case MM_MODEM_PROP_DATA_DEVICE: @@ -3926,6 +4901,37 @@ get_property (GObject *object, guint prop_id, else g_value_set_uint (value, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN); break; + case MM_GENERIC_GSM_PROP_SIM_IDENTIFIER: + g_value_set_string (value, priv->simid); + break; + case MM_GENERIC_GSM_PROP_LOC_CAPABILITIES: + g_value_set_uint (value, priv->loc_caps); + break; + case MM_GENERIC_GSM_PROP_LOC_ENABLED: + g_value_set_boolean (value, priv->loc_enabled); + break; + case MM_GENERIC_GSM_PROP_LOC_SIGNAL: + g_value_set_boolean (value, priv->loc_signal); + break; + case MM_GENERIC_GSM_PROP_LOC_LOCATION: + /* We don't allow property accesses unless location change signalling + * is enabled, for security reasons. + */ + if (priv->loc_enabled && priv->loc_signal) + locations = make_location_hash (MM_GENERIC_GSM (object), NULL); + else + locations = g_hash_table_new (g_direct_hash, g_direct_equal); + g_value_take_boxed (value, locations); + break; + case MM_GENERIC_GSM_PROP_USSD_STATE: + g_value_set_string (value, ussd_state_to_string (priv->ussd_state)); + break; + case MM_GENERIC_GSM_PROP_USSD_NETWORK_REQUEST: + g_value_set_string (value, ""); + break; + case MM_GENERIC_GSM_PROP_USSD_NETWORK_NOTIFICATION: + g_value_set_string (value, ""); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -3958,6 +4964,7 @@ finalize (GObject *object) g_free (priv->oper_code); g_free (priv->oper_name); + g_free (priv->simid); G_OBJECT_CLASS (mm_generic_gsm_parent_class)->finalize (object); } @@ -3978,6 +4985,7 @@ mm_generic_gsm_class_init (MMGenericGsmClass *klass) klass->do_enable = real_do_enable; klass->do_enable_power_up_done = real_do_enable_power_up_done; klass->do_disconnect = real_do_disconnect; + klass->get_sim_iccid = real_get_sim_iccid; /* Properties */ g_object_class_override_property (object_class, @@ -4004,6 +5012,38 @@ mm_generic_gsm_class_init (MMGenericGsmClass *klass) MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY, MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY); + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_SIM_IDENTIFIER, + MM_MODEM_GSM_CARD_SIM_IDENTIFIER); + + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_LOC_CAPABILITIES, + MM_MODEM_LOCATION_CAPABILITIES); + + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_LOC_ENABLED, + MM_MODEM_LOCATION_ENABLED); + + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_LOC_SIGNAL, + MM_MODEM_LOCATION_SIGNALS_LOCATION); + + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_LOC_LOCATION, + MM_MODEM_LOCATION_LOCATION); + + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_USSD_STATE, + MM_MODEM_GSM_USSD_STATE); + + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_USSD_NETWORK_NOTIFICATION, + MM_MODEM_GSM_USSD_NETWORK_NOTIFICATION); + + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_USSD_NETWORK_REQUEST, + MM_MODEM_GSM_USSD_NETWORK_REQUEST); + g_object_class_install_property (object_class, MM_GENERIC_GSM_PROP_POWER_UP_CMD, g_param_spec_string (MM_GENERIC_GSM_POWER_UP_CMD, |