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.
This commit is contained in:
Dan Williams
2010-03-06 10:28:11 -08:00
parent 31fb97919c
commit 54c1d069eb
6 changed files with 334 additions and 106 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,
@@ -102,15 +121,6 @@ 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)
{
@@ -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 (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 (reg_status >= 0) {
/* Update cached registration status */
status_done = reg_status_updated (self, reg_status, &info->error);
/* 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);

View File

@@ -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);