diff options
author | Dan Williams <dcbw@redhat.com> | 2010-03-06 10:28:11 -0800 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2010-03-06 10:28:11 -0800 |
commit | 54c1d069eb688f60cc721fb435953dfaebcfa6d7 (patch) | |
tree | 03253e963d96e1dfba83b54a323251f70e7473bb | |
parent | 31fb97919cd7d95d1f9b23bd0c2428d8a19dc3e4 (diff) |
gsm: rework registration handling
First, generically handle registration polling if the device does
not support unsolicited registration. Second, using the new
creg/cgreg parsing functions from mm-modem-helpers.c, handle
CREG=2 unsolicited registration replies to capture the GSM LAC/CI
for the location information API.
Because of these changes we can simplify the registration polling
during connection as well by using the common registration parsing
code and the cached registration state.
-rw-r--r-- | plugins/mm-modem-hso.c | 2 | ||||
-rw-r--r-- | plugins/mm-modem-huawei-gsm.c | 2 | ||||
-rw-r--r-- | plugins/mm-modem-mbm.c | 2 | ||||
-rw-r--r-- | plugins/mm-modem-zte.c | 1 | ||||
-rw-r--r-- | src/mm-generic-gsm.c | 428 | ||||
-rw-r--r-- | src/mm-generic-gsm.h | 5 |
6 files changed, 334 insertions, 106 deletions
diff --git a/plugins/mm-modem-hso.c b/plugins/mm-modem-hso.c index 8bd42889..a3fb95c4 100644 --- a/plugins/mm-modem-hso.c +++ b/plugins/mm-modem-hso.c @@ -647,8 +647,6 @@ grab_port (MMModem *modem, if (ptype == MM_PORT_TYPE_PRIMARY) { GRegex *regex; - mm_generic_gsm_set_unsolicited_registration (gsm, TRUE); - regex = g_regex_new ("_OWANCALL: (\\d),\\s*(\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, connection_enabled, modem, NULL); g_regex_unref (regex); diff --git a/plugins/mm-modem-huawei-gsm.c b/plugins/mm-modem-huawei-gsm.c index d450f258..76a2d76b 100644 --- a/plugins/mm-modem-huawei-gsm.c +++ b/plugins/mm-modem-huawei-gsm.c @@ -560,8 +560,6 @@ grab_port (MMModem *modem, if (ptype == MM_PORT_TYPE_SECONDARY) { GRegex *regex; - mm_generic_gsm_set_unsolicited_registration (MM_GENERIC_GSM (modem), TRUE); - regex = g_regex_new ("\\r\\n\\^RSSI:(\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, handle_signal_quality_change, modem, NULL); g_regex_unref (regex); diff --git a/plugins/mm-modem-mbm.c b/plugins/mm-modem-mbm.c index eb98990c..417457f7 100644 --- a/plugins/mm-modem-mbm.c +++ b/plugins/mm-modem-mbm.c @@ -747,8 +747,6 @@ grab_port (MMModem *modem, if (port && MM_IS_SERIAL_PORT (port) && (ptype == MM_PORT_TYPE_PRIMARY)) { GRegex *regex; - mm_generic_gsm_set_unsolicited_registration (MM_GENERIC_GSM (modem), TRUE); - regex = g_regex_new ("\\r\\n\\*EMRDY: \\d\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, mbm_emrdy_received, modem, NULL); g_regex_unref (regex); diff --git a/plugins/mm-modem-zte.c b/plugins/mm-modem-zte.c index 4412fa22..663a728e 100644 --- a/plugins/mm-modem-zte.c +++ b/plugins/mm-modem-zte.c @@ -211,7 +211,6 @@ grab_port (MMModem *modem, if (port && MM_IS_SERIAL_PORT (port)) { GRegex *regex; - mm_generic_gsm_set_unsolicited_registration (gsm, TRUE); g_object_set (port, MM_PORT_CARRIER_DETECT, FALSE, NULL); regex = g_regex_new ("\\r\\n\\+ZUSIMR:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index 84d4136c..37ad5a9e 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -28,6 +28,7 @@ #include "mm-callback-info.h" #include "mm-serial-parsers.h" #include "mm-modem-helpers.h" +#include "mm-options.h" static void modem_init (MMModem *modem_class); static void modem_gsm_card_init (MMModemGsmCard *gsm_card_class); @@ -57,7 +58,16 @@ typedef struct { char *oper_code; char *oper_name; guint32 ip_method; - gboolean unsolicited_registration; + + GPtrArray *reg_regex; + + /* CREG and CGREG info */ + guint reg_poll_id; + guint greg_poll_id; + /* Index 0 for CREG, index 1 for CGREG */ + gulong lac[2]; + gulong cell_id[2]; + gint access_tech[2]; MMModemGsmNetworkRegStatus reg_status; guint pending_reg_id; @@ -86,6 +96,15 @@ static void reg_state_changed (MMSerialPort *port, GMatchInfo *match_info, gpointer user_data); +static void get_reg_status_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data); + +static gboolean handle_reg_status_response (MMGenericGsm *self, + GString *response, + GError **error); + MMModem * mm_generic_gsm_new (const char *device, const char *driver, @@ -103,15 +122,6 @@ mm_generic_gsm_new (const char *device, } 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)); @@ -379,14 +389,22 @@ mm_generic_gsm_grab_port (MMGenericGsm *self, port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype); if (port && MM_IS_SERIAL_PORT (port)) { + GPtrArray *array; + int i; + 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); - 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_serial_port_add_unsolicited_msg_handler (MM_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); @@ -472,11 +490,174 @@ release_port (MMModem *modem, const char *subsys, const char *name) check_valid (MM_GENERIC_GSM (modem)); } +static void +reg_poll_response (MMSerialPort *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 gboolean +greg_poll_cb (gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMSerialPort *port = priv->primary; + + if (mm_port_get_connected (MM_PORT (priv->primary))) { + if (!priv->secondary) + return TRUE; /* oh well, try later */ + + /* Use secondary port if primary is connected */ + port = priv->secondary; + } + + mm_serial_port_queue_command (port, "+CGREG?", 10, reg_poll_response, self); + + return TRUE; /* continue running */ +} + +static gboolean +reg_poll_cb (gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMSerialPort *port = priv->primary; + + if (mm_port_get_connected (MM_PORT (priv->primary))) { + if (!priv->secondary) + return TRUE; /* oh well, try later */ + + /* Use secondary port if primary is connected */ + port = priv->secondary; + } + + mm_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, self); + + return TRUE; /* continue running */ +} + +static void +greg1_done (MMSerialPort *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->greg_poll_id = g_timeout_add_seconds (10, greg_poll_cb, info->modem); + } + /* Success; get initial state */ + greg_poll_cb (info->modem); + } + mm_callback_info_schedule (info); +} + +static void +greg2_done (MMSerialPort *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_serial_port_queue_command (port, "+CGREG=1", 3, greg1_done, info); + } else { + /* Success; get initial state */ + greg_poll_cb (info->modem); + + /* All done */ + mm_callback_info_schedule (info); + } + } else { + /* Modem got removed */ + mm_callback_info_schedule (info); + } +} + +static void +creg1_done (MMSerialPort *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->reg_poll_id = g_timeout_add_seconds (10, reg_poll_cb, info->modem); + reg_poll_cb (info->modem); + } + /* Success; get initial state */ + reg_poll_cb (info->modem); + + /* Now try to set up CGREG messages */ + mm_serial_port_queue_command (port, "+CGREG=2", 3, greg2_done, info); + } else { + /* Modem got removed */ + mm_callback_info_schedule (info); + } +} + +static void +creg2_done (MMSerialPort *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_serial_port_queue_command (port, "+CREG=1", 3, creg1_done, info); + } else { + /* Success; get initial state */ + reg_poll_cb (info->modem); + + /* Now try to set up CGREG messages */ + mm_serial_port_queue_command (port, "+CGREG=2", 3, greg2_done, info); + } + } else { + /* Modem got removed */ + mm_callback_info_schedule (info); + } +} + void mm_generic_gsm_enable_complete (MMGenericGsm *modem, GError *error, MMCallbackInfo *info) { + MMGenericGsmPrivate *priv; + g_return_if_fail (modem != NULL); g_return_if_fail (MM_IS_GENERIC_GSM (modem)); g_return_if_fail (info != NULL); @@ -492,7 +673,8 @@ mm_generic_gsm_enable_complete (MMGenericGsm *modem, mm_generic_gsm_update_enabled_state (modem, FALSE, MM_MODEM_STATE_REASON_NONE); } - mm_callback_info_schedule (info); + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + mm_serial_port_queue_command (priv->primary, "+CREG=2", 3, creg2_done, info); } static void @@ -547,11 +729,6 @@ init_done (MMSerialPort *port, mm_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); @@ -690,6 +867,23 @@ disable (MMModem *modem, mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0); mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + if (priv->reg_poll_id) { + g_source_remove (priv->reg_poll_id); + priv->reg_poll_id = 0; + } + + if (priv->greg_poll_id) { + g_source_remove (priv->greg_poll_id); + priv->greg_poll_id = 0; + } + + priv->lac[0] = 0; + priv->lac[1] = 0; + priv->cell_id[0] = 0; + priv->cell_id[1] = 0; + priv->access_tech[0] = -1; + priv->access_tech[1] = -1; + info = mm_callback_info_new (modem, callback, user_data); /* Cache the previous state so we can reset it if the operation fails */ @@ -1238,14 +1432,22 @@ reg_state_changed (MMSerialPort *port, { 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 access_tech = -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, &access_tech, &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, 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. @@ -1255,6 +1457,11 @@ reg_state_changed (MMSerialPort *port, priv->pending_reg_id = 0; } } + + idx = cgreg ? 1 : 0; + priv->lac[idx] = lac; + priv->cell_id[idx] = cell_id; + priv->access_tech[idx] = access_tech; } static gboolean @@ -1281,6 +1488,56 @@ 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, + "Could not parse the 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; + if (act >= 0) /* Let subclasses handle if they want */ + priv->access_tech[idx] = act; + + if ((cgreg == FALSE) && status >= 0) { + /* Update cached registration status */ + reg_status_updated (self, status, NULL); + } + + return TRUE; +} + static void get_reg_status_done (MMSerialPort *port, GString *response, @@ -1290,72 +1547,46 @@ 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; - gboolean status_done; + + /* 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); - } else { - info->error = g_error_new_literal (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Could not parse the registration status response"); - } - g_match_info_free (match_info); - g_regex_unref (r); - } + /* 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 (reg_status >= 0) { - /* Update cached registration status */ - status_done = reg_status_updated (self, reg_status, &info->error); + 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; + } - /* If we're waiting for automatic registration to complete and it's - * not done yet, check again in a few seconds. + if ( priv->reg_status != MM_MODEM_GSM_NETWORK_REG_STATUS_HOME + && priv->reg_status != MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING + && priv->reg_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. */ - if ((info == priv->pending_reg_info) && !status_done) { - g_clear_error (&info->error); - - /* Not registered yet; poll registration status again */ - 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); - return; - } + 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); + return; } reg_done: - if (info == priv->pending_reg_info) { - /* For pending registration, this will schedule the callback for us */ - mm_generic_gsm_pending_registration_stop (self); - } else { - /* Otherwise for a direct registration request, schedule the callback now */ - mm_callback_info_schedule (info); - } + /* This will schedule the pending registration's the callback for us */ + mm_generic_gsm_pending_registration_stop (self); } static void @@ -1384,7 +1615,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); } } @@ -1469,28 +1702,16 @@ get_registration_info (MMModemGsmNetwork *self, MMModemGsmNetworkRegInfoFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); MMCallbackInfo *info; - MMSerialPort *port = priv->primary; info = mm_callback_info_new_full (MM_MODEM (self), gsm_network_reg_info_invoke, G_CALLBACK (callback), user_data); - - if (mm_port_get_connected (MM_PORT (priv->primary))) { - if (!priv->secondary) { - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot get registration info while connected"); - mm_callback_info_schedule (info); - return; - } - - /* Use secondary port if primary is connected */ - port = priv->secondary; - } - - get_registration_status (port, info); + /* 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); } void @@ -2305,6 +2526,11 @@ modem_simple_init (MMModemSimple *class) static void mm_generic_gsm_init (MMGenericGsm *self) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + priv->access_tech[0] = -1; + priv->access_tech[1] = -1; + priv->reg_regex = mm_gsm_creg_regex_get (TRUE); } static void @@ -2383,6 +2609,18 @@ finalize (GObject *object) if (priv->pin_check_timeout) g_source_remove (priv->pin_check_timeout); + if (priv->reg_poll_id) { + g_source_remove (priv->reg_poll_id); + priv->reg_poll_id = 0; + } + + if (priv->greg_poll_id) { + g_source_remove (priv->greg_poll_id); + priv->greg_poll_id = 0; + } + + mm_gsm_creg_regex_destroy (priv->reg_regex); + g_free (priv->oper_code); g_free (priv->oper_name); diff --git a/src/mm-generic-gsm.h b/src/mm-generic-gsm.h index 7589cf98..5e07b77d 100644 --- a/src/mm-generic-gsm.h +++ b/src/mm-generic-gsm.h @@ -88,10 +88,7 @@ MMModem *mm_generic_gsm_new (const char *device, #define MM_GENERIC_GSM_PREV_STATE_TAG "prev-state" -void mm_generic_gsm_set_unsolicited_registration (MMGenericGsm *modem, - gboolean enabled); - -void mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem); +void mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem); void mm_generic_gsm_set_cid (MMGenericGsm *modem, guint32 cid); |