From 5ceb3a2106612ededa481c4e723626d00722d8da Mon Sep 17 00:00:00 2001 From: Jeroen Elebaut Date: Tue, 24 Nov 2009 00:43:20 -0800 Subject: [PATCH 01/19] gsm: allow longer timeout for registration Often modems will scan before registering with +COPS. --- src/mm-generic-gsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index f08097f6..4644e16d 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -1004,7 +1004,7 @@ do_register (MMModemGsmNetwork *modem, else command = g_strdup ("+COPS=0,,"); - mm_serial_port_queue_command (priv->primary, command, 10, register_done, info); + mm_serial_port_queue_command (priv->primary, command, 30, register_done, info); g_free (command); } From 5175c2accb5bff5e2c78e906d00658bd9b70c07e Mon Sep 17 00:00:00 2001 From: Jeroen Elebaut Date: Tue, 24 Nov 2009 11:31:22 -0800 Subject: [PATCH 02/19] gsm: some modems dislike spaces in AT+CGDCONT 0.7 hasn't used spaces for over a year, so this is a pretty safe change. Some modems just don't like it. 0.7 commit to remove spaces: commit 0265bfe52dcc93372aff6064e849044ccb72aa1e Author: Dan Williams Date: Fri Oct 24 15:15:06 2008 +0000 2008-10-24 Dan Williams * src/nm-gsm-device.c - (set_apn): remove erroneous spaces in AT+CGDCONT command (Jerone Young) --- src/mm-generic-gsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index 4644e16d..f2053c53 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -1364,7 +1364,7 @@ cid_range_read (MMSerialPort *port, mm_callback_info_set_data (info, "cid", GUINT_TO_POINTER (cid), NULL); - command = g_strdup_printf ("+CGDCONT=%d, \"IP\", \"%s\"", cid, apn); + command = g_strdup_printf ("+CGDCONT=%d,\"IP\",\"%s\"", cid, apn); mm_serial_port_queue_command (port, command, 3, set_apn_done, info); g_free (command); } From ac9cae2bc8bc0c7ae7ae0d0cf08d4872dcd9fb60 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 24 Nov 2009 12:16:14 -0800 Subject: [PATCH 03/19] zte: retry init strings once on timeout Some ZTE devices (MF626 for example) will emit the ZPASR unsolicited response right after MM opens the port, and they will just throw the init string away. So retry the init string once; the ZTE devices will see it the second time and continue as normal. This is the MM version of NM commit 861e9689c513cbd61fa75205a681a69d4ba8236c --- plugins/mm-modem-zte.c | 44 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/plugins/mm-modem-zte.c b/plugins/mm-modem-zte.c index 536de0ec..95fb1b0a 100644 --- a/plugins/mm-modem-zte.c +++ b/plugins/mm-modem-zte.c @@ -28,6 +28,12 @@ static void modem_init (MMModem *modem_class); G_DEFINE_TYPE_EXTENDED (MMModemZte, mm_modem_zte, MM_TYPE_GENERIC_GSM, 0, G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)) +#define MM_MODEM_ZTE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_ZTE, MMModemZtePrivate)) + +typedef struct { + gboolean init_retried; +} MMModemZtePrivate; + MMModem * mm_modem_zte_new (const char *device, const char *driver, @@ -79,6 +85,10 @@ pin_check_done (MMModem *modem, GError *error, gpointer user_data) } } +static void enable_flash_done (MMSerialPort *port, + GError *error, + gpointer user_data); + static void pre_init_done (MMSerialPort *port, GString *response, @@ -86,10 +96,18 @@ pre_init_done (MMSerialPort *port, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMModemZtePrivate *priv = MM_MODEM_ZTE_GET_PRIVATE (info->modem); if (error) { - info->error = g_error_copy (error); - mm_callback_info_schedule (info); + /* Retry the init string one more time; the modem sometimes throws it away */ + if ( !priv->init_retried + && g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_RESPONSE_TIMEOUT)) { + priv->init_retried = TRUE; + enable_flash_done (port, NULL, user_data); + } else { + info->error = g_error_copy (error); + mm_callback_info_schedule (info); + } } else { /* Now check the PIN explicitly, zte doesn't seem to report that it needs it otherwise */ @@ -116,9 +134,12 @@ enable (MMModem *modem, MMModemFn callback, gpointer user_data) { + MMModemZtePrivate *priv = MM_MODEM_ZTE_GET_PRIVATE (modem); MMCallbackInfo *info; MMSerialPort *primary; + priv->init_retried = FALSE; + /* First, reset the previously used CID */ mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0); @@ -136,6 +157,21 @@ enable (MMModem *modem, mm_serial_port_flash (primary, 100, enable_flash_done, info); } +static void +disable (MMModem *modem, + MMModemFn callback, + gpointer user_data) +{ + MMModemZtePrivate *priv = MM_MODEM_ZTE_GET_PRIVATE (modem); + MMModem *parent_modem_iface; + + priv->init_retried = FALSE; + + /* Do the normal disable stuff */ + parent_modem_iface = g_type_interface_peek_parent (MM_MODEM_GET_INTERFACE (modem)); + parent_modem_iface->disable (modem, callback, user_data); +} + static gboolean grab_port (MMModem *modem, const char *subsys, @@ -189,6 +225,7 @@ static void modem_init (MMModem *modem_class) { modem_class->enable = enable; + modem_class->disable = disable; modem_class->grab_port = grab_port; } @@ -200,6 +237,9 @@ mm_modem_zte_init (MMModemZte *self) static void mm_modem_zte_class_init (MMModemZteClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + mm_modem_zte_parent_class = g_type_class_peek_parent (klass); + g_type_class_add_private (object_class, sizeof (MMModemZtePrivate)); } From 3218b94376de9598ef4530800584c30acf350810 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 24 Nov 2009 12:57:44 -0800 Subject: [PATCH 04/19] cdma: handle 1x and EVDO quality separately --- plugins/mm-modem-huawei-cdma.c | 67 ++++++++++++++++++++++++++++------ src/mm-generic-cdma.c | 39 ++++++++++++++++---- src/mm-generic-cdma.h | 3 +- 3 files changed, 88 insertions(+), 21 deletions(-) diff --git a/plugins/mm-modem-huawei-cdma.c b/plugins/mm-modem-huawei-cdma.c index 25c7b373..cc0aa4ef 100644 --- a/plugins/mm-modem-huawei-cdma.c +++ b/plugins/mm-modem-huawei-cdma.c @@ -56,25 +56,53 @@ mm_modem_huawei_cdma_new (const char *device, /* Unsolicited message handlers */ -static void -handle_signal_quality_change (MMSerialPort *port, - GMatchInfo *match_info, - gpointer user_data) +static gint +parse_quality (const char *str, const char *detail) { - MMModemHuaweiCdma *self = MM_MODEM_HUAWEI_CDMA (user_data); - char *str; - long int quality; - - str = g_match_info_fetch (match_info, 1); + long int quality = 0; errno = 0; quality = strtol (str, NULL, 10); if (errno == 0) { quality = CLAMP (quality, 0, 100); - g_debug ("Signal quality: %ld", quality); - mm_generic_cdma_update_signal_quality (MM_GENERIC_CDMA (self), (guint32) quality); + g_debug ("%s: %ld", detail, quality); + return (gint) quality; } + return -1; +} + +static void +handle_1x_quality_change (MMSerialPort *port, + GMatchInfo *match_info, + gpointer user_data) +{ + MMModemHuaweiCdma *self = MM_MODEM_HUAWEI_CDMA (user_data); + char *str; + gint quality; + + str = g_match_info_fetch (match_info, 1); + quality = parse_quality (str, "1X signal quality"); g_free (str); + + if (quality >= 0) + mm_generic_cdma_update_cdma1x_quality (MM_GENERIC_CDMA (self), (guint32) quality); +} + +static void +handle_evdo_quality_change (MMSerialPort *port, + GMatchInfo *match_info, + gpointer user_data) +{ + MMModemHuaweiCdma *self = MM_MODEM_HUAWEI_CDMA (user_data); + char *str; + gint quality; + + str = g_match_info_fetch (match_info, 1); + quality = parse_quality (str, "EVDO signal quality"); + g_free (str); + + if (quality >= 0) + mm_generic_cdma_update_evdo_quality (MM_GENERIC_CDMA (self), (guint32) quality); } /*****************************************************************************/ @@ -233,11 +261,26 @@ grab_port (MMModem *modem, port = mm_generic_cdma_grab_port (MM_GENERIC_CDMA (modem), subsys, name, suggested_type, user_data, error); if (port && MM_IS_SERIAL_PORT (port)) { + gboolean evdo0 = FALSE, evdoA = FALSE; + g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL); + /* 1x signal level */ regex = g_regex_new ("\\r\\n\\^RSSILVL:(\\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); + mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, handle_1x_quality_change, modem, NULL); g_regex_unref (regex); + + g_object_get (G_OBJECT (modem), + MM_GENERIC_CDMA_EVDO_REV0, &evdo0, + MM_GENERIC_CDMA_EVDO_REVA, &evdoA, + NULL); + + if (evdo0 || evdoA) { + /* EVDO signal level */ + regex = g_regex_new ("\\r\\n\\^HRSSILVL:(\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, handle_evdo_quality_change, modem, NULL); + g_regex_unref (regex); + } } return !!port; diff --git a/src/mm-generic-cdma.c b/src/mm-generic-cdma.c index e07c81e9..be4e0524 100644 --- a/src/mm-generic-cdma.c +++ b/src/mm-generic-cdma.c @@ -48,7 +48,8 @@ G_DEFINE_TYPE_EXTENDED (MMGenericCdma, mm_generic_cdma, MM_TYPE_MODEM_BASE, 0, #define MM_GENERIC_CDMA_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_GENERIC_CDMA, MMGenericCdmaPrivate)) typedef struct { - guint32 signal_quality; + guint32 cdma1x_quality; + guint32 evdo_quality; gboolean valid; gboolean evdo_rev0; gboolean evdo_revA; @@ -608,13 +609,33 @@ get_card_info (MMModem *modem, /*****************************************************************************/ void -mm_generic_cdma_update_signal_quality (MMGenericCdma *self, guint32 quality) +mm_generic_cdma_update_cdma1x_quality (MMGenericCdma *self, guint32 quality) { + MMGenericCdmaPrivate *priv; + g_return_if_fail (MM_IS_GENERIC_CDMA (self)); g_return_if_fail (quality >= 0 && quality <= 100); - MM_GENERIC_CDMA_GET_PRIVATE (self)->signal_quality = quality; - mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (self), quality); + priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + if (priv->cdma1x_quality != quality) { + priv->cdma1x_quality = quality; + mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (self), quality); + } +} + +void +mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality) +{ + MMGenericCdmaPrivate *priv; + + g_return_if_fail (MM_IS_GENERIC_CDMA (self)); + g_return_if_fail (quality >= 0 && quality <= 100); + + priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + if (priv->evdo_quality != quality) { + priv->evdo_quality = quality; + // FIXME: emit a signal + } } static void @@ -646,9 +667,11 @@ get_signal_quality_done (MMSerialPort *port, quality = CLAMP (quality, 0, 31) * 100 / 31; priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); - priv->signal_quality = quality; mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL); - mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (info->modem), quality); + if (priv->cdma1x_quality != quality) { + priv->cdma1x_quality = quality; + mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (info->modem), quality); + } } } else info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, @@ -669,8 +692,8 @@ get_signal_quality (MMModemCdma *modem, connected = mm_port_get_connected (MM_PORT (priv->primary)); if (connected && !priv->secondary) { - g_message ("Returning saved signal quality %d", priv->signal_quality); - callback (MM_MODEM (modem), priv->signal_quality, NULL, user_data); + g_message ("Returning saved signal quality %d", priv->cdma1x_quality); + callback (MM_MODEM (modem), priv->cdma1x_quality, NULL, user_data); return; } diff --git a/src/mm-generic-cdma.h b/src/mm-generic-cdma.h index a1200c36..8d85cb38 100644 --- a/src/mm-generic-cdma.h +++ b/src/mm-generic-cdma.h @@ -64,7 +64,8 @@ MMPort * mm_generic_cdma_grab_port (MMGenericCdma *self, MMSerialPort *mm_generic_cdma_get_port (MMGenericCdma *modem, MMPortType ptype); -void mm_generic_cdma_update_signal_quality (MMGenericCdma *self, guint32 quality); +void mm_generic_cdma_update_cdma1x_quality (MMGenericCdma *self, guint32 quality); +void mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality); /* For unsolicited 1x registration state changes */ void mm_generic_cdma_set_1x_registration_state (MMGenericCdma *self, From 8bd10ebda2a29614319a14344294f495fede0baa Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 24 Nov 2009 13:04:21 -0800 Subject: [PATCH 05/19] huawei: sometimes reports BOOT with two LFs, not LF+CR --- plugins/mm-plugin-huawei.c | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mm-plugin-huawei.c b/plugins/mm-plugin-huawei.c index b8d8424b..ad799f0a 100644 --- a/plugins/mm-plugin-huawei.c +++ b/plugins/mm-plugin-huawei.c @@ -220,6 +220,7 @@ supports_port (MMPluginBase *base, add_regex (info->serial, "\\r\\n\\^MODE:(\\d),(\\d)\\r\\n", task); add_regex (info->serial, "\\r\\n\\^DSFLOWRPT:(.+)\\r\\n", task); add_regex (info->serial, "\\r\\n\\^BOOT:.+\\r\\n", task); + add_regex (info->serial, "\\r\\r\\^BOOT:.+\\r\\r", task); info->id = g_timeout_add (5000, probe_secondary_timeout, task); From 50412ba3f9fe6b3ee33a83d452155b7f224ff719 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 24 Nov 2009 15:16:58 -0800 Subject: [PATCH 06/19] test: handle signal quality exceptions --- test/mm-test.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/mm-test.py b/test/mm-test.py index 871ba202..485f596d 100755 --- a/test/mm-test.py +++ b/test/mm-test.py @@ -66,6 +66,12 @@ def cdma_inspect(proxy, dump_private): except dbus.exceptions.DBusException, e: print "Error reading registration state: %s" % e + try: + quality = cdma.GetSignalQuality() + print "Signal quality: %d" % quality + except dbus.exceptions.DBusException, e: + print "Error reading signal quality: %s" % e + try: info = cdma.GetServingSystem() print "Class: %s" % get_cdma_band_class(info[0]) @@ -170,7 +176,11 @@ def gsm_inspect(proxy, dump_private): # Gsm.Network interface net = dbus.Interface(proxy, dbus_interface=MM_DBUS_INTERFACE_MODEM_GSM_NETWORK) - print "Signal quality: %d" % net.GetSignalQuality() + try: + quality = net.GetSignalQuality() + print "Signal quality: %d" % quality + except dbus.exceptions.DBusException, e: + print "Error reading signal quality: %s" % e print "Scanning..." results = net.Scan(timeout=120) From 19e9c0cb480851a6e64ff9b5ce33861765a6e6ee Mon Sep 17 00:00:00 2001 From: Jeroen Elebaut Date: Tue, 24 Nov 2009 15:36:32 -0800 Subject: [PATCH 07/19] serial: handle arbitrary amounts of padding in responses Firmware on some Option devices (iCON 505) likes to spray newlines all over the place. Some fixes and boundary checks by me (dcbw). --- src/mm-serial-parsers.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/mm-serial-parsers.c b/src/mm-serial-parsers.c index b36496ff..58985d90 100644 --- a/src/mm-serial-parsers.c +++ b/src/mm-serial-parsers.c @@ -26,15 +26,19 @@ response_clean (GString *response) { char *s; - /* Ends with '' */ + /* Ends with one or more '' */ s = response->str + response->len - 1; - if (*s == '\n' && *(--s) == '\r') + while ((s > response->str) && (*s == '\n') && (*(s - 1) == '\r')) { g_string_truncate (response, response->len - 2); + s -= 2; + } - /* Starts with '' */ + /* Starts with one or more '' */ s = response->str; - if (*s == '\r' && *(++s) == '\n') + while ((response->len >= 2) && (*s == '\r') && (*(s + 1) == '\n')) { g_string_erase (response, 0, 2); + s = response->str; + } } @@ -199,7 +203,7 @@ mm_serial_parser_v1_new (void) parser = g_slice_new (MMSerialParserV1); - parser->regex_ok = g_regex_new ("\\r\\nOK\\r\\n$", flags, 0, NULL); + parser->regex_ok = g_regex_new ("\\r\\nOK(\\r\\n)+$", flags, 0, NULL); parser->regex_connect = g_regex_new ("\\r\\nCONNECT.*\\r\\n", flags, 0, NULL); parser->regex_detailed_error = g_regex_new ("\\r\\n\\+CME ERROR: (\\d+)\\r\\n$", flags, 0, NULL); parser->regex_unknown_error = g_regex_new ("\\r\\n(ERROR)|(COMMAND NOT SUPPORT)\\r\\n$", flags, 0, NULL); From 8dd2421e9c7c2d8afa5af4080eabed41c5689d81 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 26 Nov 2009 13:40:32 -0800 Subject: [PATCH 08/19] core: don't allow double-scheduled callbacks If the modem becomes invalid (it crashes and resets, for example) the callback-info's modem_destroyed_cb() function will handle cleanup. Buf if the callback-info's callback does more work than just returning the result (like simple_state_machine) it could double-schedule the callback. Don't let that happen. We need better modem-removal handling, but this fixes a crash for now. --- src/mm-callback-info.c | 2 ++ src/mm-callback-info.h | 1 + 2 files changed, 3 insertions(+) diff --git a/src/mm-callback-info.c b/src/mm-callback-info.c index 0aca55da..089f0b73 100644 --- a/src/mm-callback-info.c +++ b/src/mm-callback-info.c @@ -67,6 +67,7 @@ callback_info_done (gpointer user_data) MMCallbackInfo *info = (MMCallbackInfo *) user_data; info->pending_id = 0; + info->called = TRUE; if (info->invoke_fn && info->callback) info->invoke_fn (info); @@ -94,6 +95,7 @@ mm_callback_info_schedule (MMCallbackInfo *info) { g_return_if_fail (info != NULL); g_return_if_fail (info->pending_id == 0); + g_return_if_fail (info->called == FALSE); info->pending_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, callback_info_do, info, callback_info_done); } diff --git a/src/mm-callback-info.h b/src/mm-callback-info.h index 591ac86e..783e1282 100644 --- a/src/mm-callback-info.h +++ b/src/mm-callback-info.h @@ -28,6 +28,7 @@ struct _MMCallbackInfo { MMCallbackInfoInvokeFn invoke_fn; GCallback callback; + gboolean called; gpointer user_data; GError *error; From 15595b33dc88724253a147b7894c953a5d3110e6 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 26 Nov 2009 13:52:50 -0800 Subject: [PATCH 09/19] gsm: CGDCONT parsing fixes (bgo #602552) Ignore spaces and account for random parentheses in the CGDCONT response. Also fixes parsing CGDCONT=? on nozomi. --- src/mm-generic-gsm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index f2053c53..5bd1c328 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -1319,7 +1319,7 @@ cid_range_read (MMSerialPort *port, GRegex *r; GMatchInfo *match_info; - r = g_regex_new ("\\+CGDCONT: \\((\\d+)-(\\d+)\\),\"(\\S+)\"", + r = g_regex_new ("\\+CGDCONT:\\s*\\((\\d+)-(\\d+)\\),\\(?\"(\\S+)\"", G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, &info->error); if (r) { @@ -1385,7 +1385,7 @@ existing_apns_read (MMSerialPort *port, GRegex *r; GMatchInfo *match_info; - r = g_regex_new ("\\+CGDCONT: (\\d+)\\s*,\"(\\S+)\",\"(\\S+)\",\"(\\S+)\"", + r = g_regex_new ("\\+CGDCONT:\\s*(\\d+)\\s*,\"(\\S+)\",\"(\\S+)\",\"(\\S*)\"", G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, &info->error); if (r) { From aea0be5b9a02926a1f8f72104ce32c7dabae3f84 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 26 Nov 2009 13:54:47 -0800 Subject: [PATCH 10/19] nozomi: fix detection (lp:425312) Nozomi devices aren't quite ready when the ports show up, so we have to keep trying to open the port for a few seconds and eventually it'll succeed. Should really be fixed in the driver (ie, don't create the ttys until they can actually be used) but whatever. --- src/mm-errors.c | 1 + src/mm-errors.h | 3 +- src/mm-plugin-base.c | 67 +++++++++++++++++++++++++++++++++++++------- src/mm-serial-port.c | 9 +++++- 4 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/mm-errors.c b/src/mm-errors.c index 510fa6d0..6448b807 100644 --- a/src/mm-errors.c +++ b/src/mm-errors.c @@ -39,6 +39,7 @@ mm_serial_error_get_type (void) ENUM_ENTRY (MM_SERIAL_OPEN_FAILED, "SerialOpenFailed"), ENUM_ENTRY (MM_SERIAL_SEND_FAILED, "SerialSendfailed"), ENUM_ENTRY (MM_SERIAL_RESPONSE_TIMEOUT, "SerialResponseTimeout"), + ENUM_ENTRY (MM_SERIAL_OPEN_FAILED_NO_DEVICE, "SerialOpenFailedNoDevice"), { 0, 0, 0 } }; diff --git a/src/mm-errors.h b/src/mm-errors.h index bc43d3e9..4bf8ecad 100644 --- a/src/mm-errors.h +++ b/src/mm-errors.h @@ -22,7 +22,8 @@ enum { MM_SERIAL_OPEN_FAILED = 0, MM_SERIAL_SEND_FAILED = 1, - MM_SERIAL_RESPONSE_TIMEOUT = 2 + MM_SERIAL_RESPONSE_TIMEOUT = 2, + MM_SERIAL_OPEN_FAILED_NO_DEVICE = 3 }; #define MM_SERIAL_ERROR (mm_serial_error_quark ()) diff --git a/src/mm-plugin-base.c b/src/mm-plugin-base.c index 540670aa..ad849218 100644 --- a/src/mm-plugin-base.c +++ b/src/mm-plugin-base.c @@ -85,6 +85,9 @@ typedef struct { GUdevDevice *physdev; char *driver; + guint open_id; + guint32 open_tries; + MMSerialPort *probe_port; guint32 probed_caps; ProbeState probe_state; @@ -201,7 +204,7 @@ mm_plugin_base_supports_task_init (MMPluginBaseSupportsTask *self) } static void -dispose (GObject *object) +supports_task_dispose (GObject *object) { MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (object); @@ -214,6 +217,9 @@ dispose (GObject *object) g_free (priv->probe_resp); g_clear_error (&(priv->probe_error)); + if (priv->open_id) + g_source_remove (priv->open_id); + if (priv->probe_id) g_source_remove (priv->probe_id); if (priv->probe_port) @@ -230,7 +236,7 @@ mm_plugin_base_supports_task_class_init (MMPluginBaseSupportsTaskClass *klass) g_type_class_add_private (object_class, sizeof (MMPluginBaseSupportsTaskPrivate)); /* Virtual methods */ - object_class->dispose = dispose; + object_class->dispose = supports_task_dispose; } /*****************************************************************************/ @@ -467,12 +473,52 @@ flash_done (MMSerialPort *port, GError *error, gpointer user_data) mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, user_data); } +static gboolean +try_open (gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + GError *error = NULL; + + task_priv->open_id = 0; + + if (!mm_serial_port_open (task_priv->probe_port, &error)) { + if (++task_priv->open_tries > 4) { + /* took too long to open the port; give up */ + g_warning ("(%s): failed to open after 4 tries.", + mm_port_get_device (MM_PORT (task_priv->probe_port))); + probe_complete (task); + } else if (g_error_matches (error, + MM_SERIAL_ERROR, + MM_SERIAL_OPEN_FAILED_NO_DEVICE)) { + /* this is nozomi being dumb; try again */ + task_priv->open_id = g_timeout_add_seconds (1, try_open, task); + } else { + /* some other hard error */ + probe_complete (task); + } + g_clear_error (&error); + } else { + /* success, start probing */ + GUdevDevice *port; + + port = mm_plugin_base_supports_task_get_port (task); + g_assert (port); + + g_debug ("(%s): probe requested by plugin '%s'", + g_udev_device_get_name (port), + mm_plugin_get_name (MM_PLUGIN (task_priv->plugin))); + mm_serial_port_flash (task_priv->probe_port, 100, flash_done, task); + } + + return FALSE; +} + gboolean mm_plugin_base_probe_port (MMPluginBase *self, MMPluginBaseSupportsTask *task, GError **error) { - MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); MMSerialPort *serial; const char *name; @@ -488,6 +534,12 @@ mm_plugin_base_probe_port (MMPluginBase *self, g_assert (name); serial = mm_serial_port_new (name, MM_PORT_TYPE_PRIMARY); + if (serial == NULL) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Failed to create new serial port."); + return FALSE; + } + g_object_set (serial, MM_SERIAL_PORT_SEND_DELAY, (guint64) 100000, MM_PORT_CARRIER_DETECT, FALSE, @@ -498,14 +550,9 @@ mm_plugin_base_probe_port (MMPluginBase *self, mm_serial_parser_v1_new (), mm_serial_parser_v1_destroy); - if (!mm_serial_port_open (serial, error)) { - g_object_unref (serial); - return FALSE; - } - - g_debug ("(%s): probe requested by plugin '%s'", name, priv->name); + /* Open the port */ task_priv->probe_port = serial; - mm_serial_port_flash (serial, 100, flash_done, task); + task_priv->open_id = g_idle_add (try_open, task); return TRUE; } diff --git a/src/mm-serial-port.c b/src/mm-serial-port.c index 250e00be..ad955008 100644 --- a/src/mm-serial-port.c +++ b/src/mm-serial-port.c @@ -791,11 +791,18 @@ mm_serial_port_open (MMSerialPort *self, GError **error) g_message ("(%s) opening serial device...", device); devfile = g_strdup_printf ("/dev/%s", device); + errno = 0; priv->fd = open (devfile, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY); g_free (devfile); if (priv->fd < 0) { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED, + /* nozomi isn't ready yet when the port appears, and it'll return + * ENODEV when open(2) is called on it. Make sure we can handle this + * by returning a special error in that case. + */ + g_set_error (error, + MM_SERIAL_ERROR, + (errno == ENODEV) ? MM_SERIAL_OPEN_FAILED_NO_DEVICE : MM_SERIAL_OPEN_FAILED, "Could not open serial device %s: %s", device, strerror (errno)); return FALSE; } From d5b0574f587ddab904d3a1f1af3cb2c354486f66 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 29 Nov 2009 18:56:29 -0800 Subject: [PATCH 11/19] test: handle scan errors more gracefully --- test/mm-test.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/mm-test.py b/test/mm-test.py index 485f596d..cb6bad2b 100755 --- a/test/mm-test.py +++ b/test/mm-test.py @@ -183,7 +183,12 @@ def gsm_inspect(proxy, dump_private): print "Error reading signal quality: %s" % e print "Scanning..." - results = net.Scan(timeout=120) + try: + results = net.Scan(timeout=120) + except dbus.exceptions.DBusException, e: + print "Error scanning: %s" % e + results = {} + for r in results: status = r['status'] if status == "1": From 9dbf8f22c06a204dd3cd9521c52cc5afafe8d278 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 29 Nov 2009 18:58:21 -0800 Subject: [PATCH 12/19] gsm: allow unsolicted registration updates to actually update reg state --- src/mm-generic-gsm.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index 5bd1c328..18901963 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -820,9 +820,6 @@ reg_status_updated (MMGenericGsm *self, int new_value, MMCallbackInfo *info) MMModemGsmNetworkRegStatus status; gboolean status_done = FALSE; - /* info must be set to process status updates - for now */ - g_return_val_if_fail (!!info, status_done); - switch (new_value) { case 0: status = MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE; @@ -854,25 +851,31 @@ reg_status_updated (MMGenericGsm *self, int new_value, MMCallbackInfo *info) case MM_MODEM_GSM_NETWORK_REG_STATUS_HOME: case MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING: /* Successfully registered - stop registration */ - mm_callback_info_schedule (info); + if (info) + mm_callback_info_schedule (info); mm_generic_gsm_pending_registration_stop (self); status_done = TRUE; break; case MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED: /* registration failed - stop registration */ - info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED); - mm_callback_info_schedule (info); + if (info) { + info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED); + mm_callback_info_schedule (info); + } mm_generic_gsm_pending_registration_stop (self); status_done = TRUE; break; case MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING: - info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_TIMEOUT); + if (info) + info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_TIMEOUT); break; case MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE: - info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NO_NETWORK); + if (info) + info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NO_NETWORK); break; default: - info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN); + if (info) + info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN); break; } return status_done; From c8fdb592e3500177c3267b9eefd4afebed58bb64 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 29 Nov 2009 18:58:56 -0800 Subject: [PATCH 13/19] hso: send authentication info correctly for Icera-based Option modems Icera-based modems use AT_OPDPP instead of AT$QCPDPP. --- plugins/mm-modem-hso.c | 86 +++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 23 deletions(-) diff --git a/plugins/mm-modem-hso.c b/plugins/mm-modem-hso.c index 26dc17d6..4d599c16 100644 --- a/plugins/mm-modem-hso.c +++ b/plugins/mm-modem-hso.c @@ -47,10 +47,23 @@ G_DEFINE_TYPE_EXTENDED (MMModemHso, mm_modem_hso, MM_TYPE_GENERIC_GSM, 0, #define MM_MODEM_HSO_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_HSO, MMModemHsoPrivate)) +static void _internal_hso_modem_authenticate (MMModemHso *self, MMCallbackInfo *info); + +const char *auth_commands[] = { + "$QCPDPP", + /* Icera-based devices (GI0322/Quicksilver, iCON 505) don't implement + * $QCPDPP, but instead use _OPDPP with the same arguments. + */ + "_OPDPP", + NULL +}; + typedef struct { /* Pending connection attempt */ MMCallbackInfo *connect_pending_data; guint connect_pending_id; + + guint32 auth_idx; } MMModemHsoPrivate; #define OWANDATA_TAG "_OWANDATA: " @@ -195,13 +208,55 @@ auth_done (MMSerialPort *port, { MMCallbackInfo *info = (MMCallbackInfo *) user_data; MMModemHso *self = MM_MODEM_HSO (info->modem); + MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (self); if (error) { - info->error = g_error_copy (error); - mm_callback_info_schedule (info); - } else + priv->auth_idx++; + if (auth_commands[priv->auth_idx]) { + /* Try the next auth command */ + _internal_hso_modem_authenticate (self, info); + } else { + info->error = g_error_copy (error); + mm_callback_info_schedule (info); + } + } else { + priv->auth_idx = 0; + /* success, kill any existing connections first */ hso_call_control (self, FALSE, FALSE, hso_disabled, info); + } +} + +static void +_internal_hso_modem_authenticate (MMModemHso *self, MMCallbackInfo *info) +{ + MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (self); + MMSerialPort *primary; + guint32 cid; + char *command; + const char *username, *password; + + primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (self), MM_PORT_TYPE_PRIMARY); + g_assert (primary); + + cid = hso_get_cid (self); + + username = mm_callback_info_get_data (info, "username"); + password = mm_callback_info_get_data (info, "password"); + + if (!username && !password) + command = g_strdup_printf ("%s=%d,0", auth_commands[priv->auth_idx], cid); + else { + command = g_strdup_printf ("%s=%d,1,\"%s\",\"%s\"", + auth_commands[priv->auth_idx], + cid, + password ? password : "", + username ? username : ""); + + } + + mm_serial_port_queue_command (primary, command, 3, auth_done, info); + g_free (command); } void @@ -212,32 +267,17 @@ mm_hso_modem_authenticate (MMModemHso *self, gpointer user_data) { MMCallbackInfo *info; - MMSerialPort *primary; g_return_if_fail (MM_IS_MODEM_HSO (self)); g_return_if_fail (callback != NULL); info = mm_callback_info_new (MM_MODEM (self), callback, user_data); + if (username) + mm_callback_info_set_data (info, "username", g_strdup (username), g_free); + if (password) + mm_callback_info_set_data (info, "password", g_strdup (password), g_free); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (self), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - - if (username || password) { - char *command; - - // FIXME: if QCPDPP fails, try OPDPP. AT&T Quicksilver uses a different - // chipset (ie, not Qualcomm) and the auth command is OPDPP instead of - // the Qualcomm-specific QCPDPP. - - command = g_strdup_printf ("AT$QCPDPP=%d,1,\"%s\",\"%s\"", - hso_get_cid (self), - password ? password : "", - username ? username : ""); - - mm_serial_port_queue_command (primary, command, 3, auth_done, info); - g_free (command); - } else - auth_done (primary, NULL, NULL, info); + _internal_hso_modem_authenticate (self, info); } /*****************************************************************************/ From 01db3f1c2fad716088563fb7e91720691fd962d4 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 29 Nov 2009 21:49:46 -0800 Subject: [PATCH 14/19] test: give connect a bit more time Registration can be part of the connect process, which can take quite a while. --- test/mm-test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mm-test.py b/test/mm-test.py index cb6bad2b..bbd634df 100755 --- a/test/mm-test.py +++ b/test/mm-test.py @@ -229,7 +229,7 @@ def gsm_connect(proxy, apn, user, password): # Modem.Simple interface simple = dbus.Interface(proxy, dbus_interface=MM_DBUS_INTERFACE_MODEM_SIMPLE) try: - simple.Connect({'apn': apn, 'number':"*99#"}, timeout=60) + simple.Connect({'apn': apn, 'number':"*99#"}, timeout=120) print "\nConnected!" except Exception, e: print "Error connecting: %s" % e From 3890009bcc15a9524fdd17cdb6c8ec7f1b879f31 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 29 Nov 2009 22:45:03 -0800 Subject: [PATCH 15/19] test: actually send username and password when connecting --- test/mm-test.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/mm-test.py b/test/mm-test.py index bbd634df..cb9ecf0a 100755 --- a/test/mm-test.py +++ b/test/mm-test.py @@ -229,7 +229,12 @@ def gsm_connect(proxy, apn, user, password): # Modem.Simple interface simple = dbus.Interface(proxy, dbus_interface=MM_DBUS_INTERFACE_MODEM_SIMPLE) try: - simple.Connect({'apn': apn, 'number':"*99#"}, timeout=120) + opts = {'apn': apn, 'number':"*99#"} + if user is not None: + opts['username'] = user + if password is not None: + opts['password'] = password + simple.Connect(opts, timeout=120) print "\nConnected!" except Exception, e: print "Error connecting: %s" % e From 028c6a5e4eaf487bbb18debacbc0c56c32f8f52b Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 29 Nov 2009 22:45:28 -0800 Subject: [PATCH 16/19] hso: fix up connection issues and error ignorance Should have ignored errors when cleaning up old contexts that may or may not exist. Rename hso_disable() to something more appropriate since it's actually part of the enable/connect path, not the disable path. --- plugins/mm-modem-hso.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/plugins/mm-modem-hso.c b/plugins/mm-modem-hso.c index 4d599c16..089f8371 100644 --- a/plugins/mm-modem-hso.c +++ b/plugins/mm-modem-hso.c @@ -85,6 +85,8 @@ mm_modem_hso_new (const char *device, NULL)); } +#define IGNORE_ERRORS_TAG "ignore-errors" + static void hso_call_control_done (MMSerialPort *port, GString *response, @@ -93,7 +95,7 @@ hso_call_control_done (MMSerialPort *port, { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - if (error && !mm_callback_info_get_data (info, "ignore-errors")) + if (error && !mm_callback_info_get_data (info, IGNORE_ERRORS_TAG)) info->error = g_error_copy (error); mm_callback_info_schedule (info); @@ -123,7 +125,7 @@ hso_call_control (MMModemHso *self, MMSerialPort *primary; info = mm_callback_info_new (MM_MODEM (self), callback, user_data); - mm_callback_info_set_data (info, "ignore-error", GUINT_TO_POINTER (ignore_errors), NULL); + mm_callback_info_set_data (info, IGNORE_ERRORS_TAG, GUINT_TO_POINTER (ignore_errors), NULL); command = g_strdup_printf ("AT_OWANCALL=%d,%d,1", hso_get_cid (self), activate ? 1 : 0); primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (self), MM_PORT_TYPE_PRIMARY); @@ -185,9 +187,9 @@ hso_enabled (MMModem *modem, } static void -hso_disabled (MMModem *modem, - GError *error, - gpointer user_data) +clear_old_context (MMModem *modem, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; @@ -223,7 +225,7 @@ auth_done (MMSerialPort *port, priv->auth_idx = 0; /* success, kill any existing connections first */ - hso_call_control (self, FALSE, FALSE, hso_disabled, info); + hso_call_control (self, FALSE, TRUE, clear_old_context, info); } } @@ -344,7 +346,7 @@ disable (MMModem *modem, info = mm_callback_info_new (modem, callback, user_data); /* Kill any existing connection */ - hso_call_control (MM_MODEM_HSO (modem), FALSE, FALSE, disable_done, info); + hso_call_control (MM_MODEM_HSO (modem), FALSE, TRUE, disable_done, info); } static void From d06f8f46c68bd561b349f2db78d3caca531d3a4f Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 29 Nov 2009 22:46:48 -0800 Subject: [PATCH 17/19] serial: don't run commands when there's already one in-progress If there's already a command in-progress don't try to send another until the previous one has timed out. Also use g_timeout_add_seconds() since precision doesn't really matter for command timeouts. --- src/mm-serial-port.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/mm-serial-port.c b/src/mm-serial-port.c index ad955008..3abd5619 100644 --- a/src/mm-serial-port.c +++ b/src/mm-serial-port.c @@ -479,9 +479,15 @@ mm_serial_port_schedule_queue_process (MMSerialPort *self) MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); GSource *source; - if (priv->queue_schedule) + if (priv->timeout_id) { + /* A command is already in progress */ + return; + } + + if (priv->queue_schedule) { /* Already scheduled */ return; + } source = g_idle_source_new (); g_source_set_closure (source, g_cclosure_new_object (G_CALLBACK (mm_serial_port_queue_process), G_OBJECT (self))); @@ -568,7 +574,7 @@ mm_serial_port_queue_process (gpointer data) if (mm_serial_port_send_command (self, info->command, &error)) { GSource *source; - source = g_timeout_source_new (info->timeout); + source = g_timeout_source_new_seconds (info->timeout); g_source_set_closure (source, g_cclosure_new_object (G_CALLBACK (mm_serial_port_timed_out), G_OBJECT (self))); g_source_attach (source, NULL); priv->timeout_id = g_source_get_id (source); @@ -896,7 +902,7 @@ internal_queue_command (MMSerialPort *self, info = g_slice_new0 (MMQueueData); info->command = g_strdup (command); info->cached = cached; - info->timeout = timeout_seconds * 1000; + info->timeout = timeout_seconds; info->callback = callback; info->user_data = user_data; From f205c814b58d37b3865c42e7f8b5112ce0f78aa0 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Mon, 30 Nov 2009 09:47:39 -0800 Subject: [PATCH 18/19] hso: ensure authentication works again after auth errors --- plugins/mm-modem-hso.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mm-modem-hso.c b/plugins/mm-modem-hso.c index 089f8371..a2e3b408 100644 --- a/plugins/mm-modem-hso.c +++ b/plugins/mm-modem-hso.c @@ -218,6 +218,9 @@ auth_done (MMSerialPort *port, /* Try the next auth command */ _internal_hso_modem_authenticate (self, info); } else { + /* Reset to 0 so that something gets tried for the next connection */ + priv->auth_idx = 0; + info->error = g_error_copy (error); mm_callback_info_schedule (info); } From 0f595adb7f07f575627667480f23775b21f9efb2 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Mon, 30 Nov 2009 15:41:09 -0800 Subject: [PATCH 19/19] gsm: fix unsolicited registration segfaults By decoupling the solicited registration callback from unsolicited replies, we can be sure of the call flow and avoid issues where unsolicited registration will be processed when an explicit registration request is no longer in progress. Also ups the timeout on CREG=0,, to 120 seconds because that appears to trigger an internal scan on some of the 'hso' devices that I have, and can take up to 60 or more seconds to complete or fail. --- src/mm-generic-gsm.c | 128 ++++++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 43 deletions(-) diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index 18901963..95b5d883 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -801,6 +801,7 @@ read_operator_name_done (MMSerialPort *port, } /* Registration */ +#define REG_STATUS_AGAIN_TAG "reg-status-again" void mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem) @@ -808,14 +809,23 @@ mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem) MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); if (priv->pending_reg_id) { + /* Clear the registration timeout handler */ g_source_remove (priv->pending_reg_id); priv->pending_reg_id = 0; + } + + if (priv->pending_reg_info) { + /* Clear any ongoing registration status callback */ + mm_callback_info_set_data (priv->pending_reg_info, REG_STATUS_AGAIN_TAG, NULL, NULL); + + /* And schedule the callback */ + mm_callback_info_schedule (priv->pending_reg_info); priv->pending_reg_info = NULL; } } static gboolean -reg_status_updated (MMGenericGsm *self, int new_value, MMCallbackInfo *info) +reg_status_updated (MMGenericGsm *self, int new_value, GError **error) { MMModemGsmNetworkRegStatus status; gboolean status_done = FALSE; @@ -851,31 +861,25 @@ reg_status_updated (MMGenericGsm *self, int new_value, MMCallbackInfo *info) case MM_MODEM_GSM_NETWORK_REG_STATUS_HOME: case MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING: /* Successfully registered - stop registration */ - if (info) - mm_callback_info_schedule (info); - mm_generic_gsm_pending_registration_stop (self); status_done = TRUE; break; case MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED: /* registration failed - stop registration */ - if (info) { - info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED); - mm_callback_info_schedule (info); - } - mm_generic_gsm_pending_registration_stop (self); + if (error) + *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED); status_done = TRUE; break; case MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING: - if (info) - info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_TIMEOUT); + if (error) + *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_TIMEOUT); break; case MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE: - if (info) - info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NO_NETWORK); + if (error) + *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NO_NETWORK); break; default: - if (info) - info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN); + if (error) + *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN); break; } return status_done; @@ -887,23 +891,21 @@ reg_state_changed (MMSerialPort *port, gpointer user_data) { MMGenericGsm *self = MM_GENERIC_GSM (user_data); - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); char *str; str = g_match_info_fetch (match_info, 1); - if (!reg_status_updated (self, atoi (str), priv->pending_reg_info) && priv->pending_reg_info) - g_clear_error (&priv->pending_reg_info->error); - + reg_status_updated (self, atoi (str), NULL); g_free (str); } static gboolean reg_status_again (gpointer data) { - MMGenericGsmPrivate *priv; MMCallbackInfo *info = (MMCallbackInfo *) data; + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + g_warn_if_fail (info == priv->pending_reg_info); - priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); if (priv->pending_reg_id) get_registration_status (priv->primary, info); @@ -913,7 +915,11 @@ reg_status_again (gpointer data) static void reg_status_again_remove (gpointer data) { - g_source_remove (GPOINTER_TO_INT (data)); + guint id = GPOINTER_TO_UINT (data); + + /* Technically the GSource ID can be 0, but in practice it won't be */ + if (id > 0) + g_source_remove (id); } static void @@ -925,41 +931,72 @@ 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 unsolicited, stat; + int reg_status = -1; + GRegex *r; + GMatchInfo *match_info; + char *tmp; guint id; + g_warn_if_fail (info == priv->pending_reg_info); + if (error) { info->error = g_error_copy (error); - mm_generic_gsm_pending_registration_stop (self); - mm_callback_info_schedule (info); - return; + goto reg_done; } - if ( !g_str_has_prefix (response->str, "+CREG: ") - || (sscanf (response->str + 7, "%d,%d", &unsolicited, &stat) != 2)) { - g_debug ("%s: unknown response: %s", __func__, response->str); - info->error = g_error_new_literal (MM_MODEM_ERROR, + 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, - "Could not parse the response"); - mm_generic_gsm_pending_registration_stop (self); - mm_callback_info_schedule (info); + "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); + } + + if ( reg_status >= 0 + && !reg_status_updated (self, reg_status, &info->error) + && priv->pending_reg_id) { + 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; } - if (!reg_status_updated (self, stat, info) && priv->pending_reg_id) { - g_clear_error (&info->error); - /* Registration is still going */ - id = g_timeout_add_seconds (1, reg_status_again, info); - mm_callback_info_set_data (info, "reg-status-again", - GINT_TO_POINTER (id), - reg_status_again_remove); - } +reg_done: + /* This will schedule the callback for us */ + mm_generic_gsm_pending_registration_stop (self); } static void get_registration_status (MMSerialPort *port, MMCallbackInfo *info) { - mm_serial_port_queue_command (port, "+CREG?", 3, get_reg_status_done, info); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + g_warn_if_fail (info == priv->pending_reg_info); + + mm_serial_port_queue_command (port, "+CREG?", 10, get_reg_status_done, info); } static void @@ -978,6 +1015,8 @@ registration_timed_out (gpointer data) MMCallbackInfo *info = (MMCallbackInfo *) data; MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + g_warn_if_fail (info == priv->pending_reg_info); + priv->pending_reg_id = 0; priv->pending_reg_info = NULL; priv->reg_status = MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE; @@ -997,6 +1036,9 @@ do_register (MMModemGsmNetwork *modem, MMCallbackInfo *info; char *command; + /* Clear any previous registration */ + mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); priv->pending_reg_id = g_timeout_add_seconds (60, registration_timed_out, info); @@ -1007,7 +1049,7 @@ do_register (MMModemGsmNetwork *modem, else command = g_strdup ("+COPS=0,,"); - mm_serial_port_queue_command (priv->primary, command, 30, register_done, info); + mm_serial_port_queue_command (priv->primary, command, 120, register_done, info); g_free (command); }