aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2014-02-05 08:38:23 +0100
committerGuido Günther <agx@sigxcpu.org>2014-02-05 08:38:23 +0100
commit7fbee6ce27176bfc7ae9b34a4de9452cf5f6fa43 (patch)
tree5cabb0ab457846912d99ef675160be0dce4fbe6f /src
parentdc645b92b9a7db3076ae34986ac219d01677d124 (diff)
Imported Upstream version 0.4+git.20110124t203624.00b6cceupstream/0.4+git.20110124t203624.00b6cce
Diffstat (limited to 'src')
-rw-r--r--src/77-mm-usb-device-blacklist.rules6
-rw-r--r--src/80-mm-candidate.rules16
-rw-r--r--src/Makefile.am92
-rw-r--r--src/main.c132
-rw-r--r--src/mm-at-serial-port.c12
-rw-r--r--src/mm-auth-provider-polkit.c27
-rw-r--r--src/mm-auth-provider.h1
-rw-r--r--src/mm-charsets.c353
-rw-r--r--src/mm-charsets.h16
-rw-r--r--src/mm-generic-cdma.c285
-rw-r--r--src/mm-generic-cdma.h8
-rw-r--r--src/mm-generic-gsm.c1310
-rw-r--r--src/mm-generic-gsm.h31
-rw-r--r--src/mm-log.c233
-rw-r--r--src/mm-log.h61
-rw-r--r--src/mm-manager.c119
-rw-r--r--src/mm-modem-base.c201
-rw-r--r--src/mm-modem-cdma.c44
-rw-r--r--src/mm-modem-cdma.h26
-rw-r--r--src/mm-modem-gsm-card.c8
-rw-r--r--src/mm-modem-gsm-card.h1
-rw-r--r--src/mm-modem-gsm-network.c2
-rw-r--r--src/mm-modem-gsm-ussd.c378
-rw-r--r--src/mm-modem-gsm-ussd.h75
-rw-r--r--src/mm-modem-gsm.h3
-rw-r--r--src/mm-modem-helpers.c296
-rw-r--r--src/mm-modem-helpers.h18
-rw-r--r--src/mm-modem-location.c5
-rw-r--r--src/mm-modem-location.h7
-rw-r--r--src/mm-modem.c99
-rw-r--r--src/mm-modem.h22
-rw-r--r--src/mm-options.c55
-rw-r--r--src/mm-options.h23
-rw-r--r--src/mm-plugin-base.c91
-rw-r--r--src/mm-port.c30
-rw-r--r--src/mm-port.h2
-rw-r--r--src/mm-properties-changed-signal.c150
-rw-r--r--src/mm-properties-changed-signal.h18
-rw-r--r--src/mm-qcdm-serial-port.c134
-rw-r--r--src/mm-qcdm-serial-port.h2
-rw-r--r--src/mm-serial-parsers.c28
-rw-r--r--src/mm-serial-port.c87
-rw-r--r--src/mm-serial-port.h1
-rw-r--r--src/mm-utils.c14
-rw-r--r--src/mm-utils.h2
-rw-r--r--src/tests/Makefile.am31
-rw-r--r--src/tests/test-charsets.c323
-rw-r--r--src/tests/test-modem-helpers.c412
-rw-r--r--src/tests/test-qcdm-serial-port.c482
49 files changed, 5097 insertions, 675 deletions
diff --git a/src/77-mm-usb-device-blacklist.rules b/src/77-mm-usb-device-blacklist.rules
index 78a6770..58b0cee 100644
--- a/src/77-mm-usb-device-blacklist.rules
+++ b/src/77-mm-usb-device-blacklist.rules
@@ -62,5 +62,11 @@ ATTRS{idVendor}=="0592", ATTRS{idProduct}=="0002", ENV{ID_MM_DEVICE_IGNORE}="1"
# that isn't blacklisted.
ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0061", ENV{ID_MM_DEVICE_IGNORE}="1"
+# Belkin F5U183 Serial Adapter (unlikely to have a modem behind it)
+ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0103", ENV{ID_MM_DEVICE_IGNORE}="1"
+
+# ATEN Intl UC-232A (Prolific)
+ATTRS{idVendor}=="0557", ATTRS{idProduct}=="2008", ENV{ID_MM_DEVICE_IGNORE}="1"
+
LABEL="mm_usb_device_blacklist_end"
diff --git a/src/80-mm-candidate.rules b/src/80-mm-candidate.rules
new file mode 100644
index 0000000..e99ae3a
--- /dev/null
+++ b/src/80-mm-candidate.rules
@@ -0,0 +1,16 @@
+# do not edit this file, it will be overwritten on update
+
+# Tag any devices that MM might be interested in; if ModemManager is started
+# up right after udev, when MM explicitly requests devices on startup it may
+# get devices that haven't had all rules run yet. Thus, we tag devices we're
+# interested in and when handling devices during MM startup we ignore any
+# that don't have this tag. MM will still get the udev 'add' event for the
+# device a short while later and then process it as normal.
+
+ACTION!="add|change|move", GOTO="mm_candidate_end"
+
+SUBSYSTEM=="tty", ENV{ID_MM_CANDIDATE}="1"
+SUBSYSTEM=="net", ENV{ID_MM_CANDIDATE}="1"
+
+LABEL="mm_candidate_end"
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 2061ae8..e813e7e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,12 +4,13 @@ udevrulesdir = $(UDEV_BASE_DIR)/rules.d
udevrules_DATA = \
77-mm-usb-device-blacklist.rules \
77-mm-pcmcia-device-blacklist.rules \
- 77-mm-platform-serial-whitelist.rules
+ 77-mm-platform-serial-whitelist.rules \
+ 80-mm-candidate.rules
EXTRA_DIST = \
$(udevrules_DATA)
-noinst_LTLIBRARIES = libmodem-helpers.la
+noinst_LTLIBRARIES = libmodem-helpers.la libserial.la
libmodem_helpers_la_CPPFLAGS = \
$(MM_CFLAGS)
@@ -24,6 +25,20 @@ libmodem_helpers_la_SOURCES = \
mm-utils.c \
mm-utils.h
+libserial_la_CPPFLAGS = \
+ $(MM_CFLAGS) \
+ -I$(top_srcdir)
+
+libserial_la_SOURCES = \
+ mm-port.c \
+ mm-port.h \
+ mm-serial-port.c \
+ mm-serial-port.h \
+ mm-at-serial-port.c \
+ mm-at-serial-port.h \
+ mm-qcdm-serial-port.c \
+ mm-qcdm-serial-port.h
+
sbin_PROGRAMS = modem-manager
modem_manager_CPPFLAGS = \
@@ -41,8 +56,9 @@ modem_manager_LDADD = \
$(MM_LIBS) \
$(GUDEV_LIBS) \
$(top_builddir)/marshallers/libmarshallers.la \
- $(top_builddir)/libqcdm/src/libqcdm.la \
- $(builddir)/libmodem-helpers.la
+ $(builddir)/libmodem-helpers.la \
+ $(builddir)/libserial.la \
+ $(top_builddir)/libqcdm/src/libqcdm.la
if WITH_POLKIT
modem_manager_LDADD += $(POLKIT_LIBS)
@@ -63,12 +79,10 @@ auth_sources += \
mm-auth-provider-polkit.h
endif
-loc_sources = \
- mm-modem-location.c \
- mm-modem-location.h
-
modem_manager_SOURCES = \
main.c \
+ mm-log.c \
+ mm-log.h \
mm-callback-info.c \
mm-callback-info.h \
$(auth_sources) \
@@ -76,14 +90,6 @@ modem_manager_SOURCES = \
mm-manager.h \
mm-modem.c \
mm-modem.h \
- mm-port.c \
- mm-port.h \
- mm-serial-port.c \
- mm-serial-port.h \
- mm-at-serial-port.c \
- mm-at-serial-port.h \
- mm-qcdm-serial-port.c \
- mm-qcdm-serial-port.h \
mm-serial-parsers.c \
mm-serial-parsers.h \
mm-modem-base.c \
@@ -101,37 +107,45 @@ modem_manager_SOURCES = \
mm-modem-gsm-network.h \
mm-modem-gsm-sms.c \
mm-modem-gsm-sms.h \
+ mm-modem-gsm-ussd.c \
+ mm-modem-gsm-ussd.h \
mm-modem-simple.c \
mm-modem-simple.h \
- mm-options.c \
- mm-options.h \
mm-plugin.c \
mm-plugin.h \
mm-plugin-base.c \
mm-plugin-base.h \
mm-properties-changed-signal.c \
- mm-properties-changed-signal.h
+ mm-properties-changed-signal.h \
+ mm-modem-location.c \
+ mm-modem-location.h
-mm-manager-glue.h: $(top_srcdir)/introspection/mm-manager.xml
- dbus-binding-tool --prefix=mm_manager --mode=glib-server --output=$@ $<
+mm-manager-glue.h: $(top_srcdir)/introspection/org.freedesktop.ModemManager.xml
+ $(AM_V_GEN) dbus-binding-tool --prefix=mm_manager --mode=glib-server --output=$@ $<
-mm-modem-glue.h: $(top_srcdir)/introspection/mm-modem.xml
- dbus-binding-tool --prefix=mm_modem --mode=glib-server --output=$@ $<
+mm-modem-glue.h: $(top_srcdir)/introspection/org.freedesktop.ModemManager.Modem.xml
+ $(AM_V_GEN) dbus-binding-tool --prefix=mm_modem --mode=glib-server --output=$@ $<
-mm-modem-simple-glue.h: $(top_srcdir)/introspection/mm-modem-simple.xml
- dbus-binding-tool --prefix=mm_modem_simple --mode=glib-server --output=$@ $<
+mm-modem-simple-glue.h: $(top_srcdir)/introspection/org.freedesktop.ModemManager.Modem.Simple.xml
+ $(AM_V_GEN) dbus-binding-tool --prefix=mm_modem_simple --mode=glib-server --output=$@ $<
-mm-modem-cdma-glue.h: $(top_srcdir)/introspection/mm-modem-cdma.xml
- dbus-binding-tool --prefix=mm_modem_cdma --mode=glib-server --output=$@ $<
+mm-modem-cdma-glue.h: $(top_srcdir)/introspection/org.freedesktop.ModemManager.Modem.Cdma.xml
+ $(AM_V_GEN) dbus-binding-tool --prefix=mm_modem_cdma --mode=glib-server --output=$@ $<
-mm-modem-gsm-card-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-card.xml
- dbus-binding-tool --prefix=mm_modem_gsm_card --mode=glib-server --output=$@ $<
+mm-modem-gsm-card-glue.h: $(top_srcdir)/introspection/org.freedesktop.ModemManager.Modem.Gsm.Card.xml
+ $(AM_V_GEN) dbus-binding-tool --prefix=mm_modem_gsm_card --mode=glib-server --output=$@ $<
-mm-modem-gsm-network-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-network.xml
- dbus-binding-tool --prefix=mm_modem_gsm_network --mode=glib-server --output=$@ $<
+mm-modem-gsm-network-glue.h: $(top_srcdir)/introspection/org.freedesktop.ModemManager.Modem.Gsm.Network.xml
+ $(AM_V_GEN) dbus-binding-tool --prefix=mm_modem_gsm_network --mode=glib-server --output=$@ $<
-mm-modem-gsm-sms-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-sms.xml
- dbus-binding-tool --prefix=mm_modem_gsm_sms --mode=glib-server --output=$@ $<
+mm-modem-gsm-sms-glue.h: $(top_srcdir)/introspection/org.freedesktop.ModemManager.Modem.Gsm.SMS.xml
+ $(AM_V_GEN) dbus-binding-tool --prefix=mm_modem_gsm_sms --mode=glib-server --output=$@ $<
+
+mm-modem-gsm-ussd-glue.h: $(top_srcdir)/introspection/org.freedesktop.ModemManager.Modem.Gsm.Ussd.xml
+ $(AM_V_GEN) dbus-binding-tool --prefix=mm_modem_gsm_ussd --mode=glib-server --output=$@ $<
+
+mm-properties-changed-glue.h: $(top_srcdir)/introspection/org.freedesktop.DBus.Properties.xml
+ $(AM_V_GEN) dbus-binding-tool --prefix=mm_properties_changed --mode=glib-server --output=$@ $<
BUILT_SOURCES = \
mm-manager-glue.h \
@@ -140,17 +154,15 @@ BUILT_SOURCES = \
mm-modem-cdma-glue.h \
mm-modem-gsm-card-glue.h \
mm-modem-gsm-network-glue.h \
- mm-modem-gsm-sms-glue.h
+ mm-modem-gsm-sms-glue.h \
+ mm-modem-gsm-ussd-glue.h \
+ mm-properties-changed-glue.h
-if WITH_LOCATION_API
-mm-modem-location-glue.h: $(top_srcdir)/introspection/mm-modem-location.xml
- dbus-binding-tool --prefix=mm_modem_location --mode=glib-server --output=$@ $<
+mm-modem-location-glue.h: $(top_srcdir)/introspection/org.freedesktop.ModemManager.Modem.Location.xml
+ $(AM_V_GEN) dbus-binding-tool --prefix=mm_modem_location --mode=glib-server --output=$@ $<
modem_manager_SOURCES += $(loc_sources)
BUILT_SOURCES += mm-modem-location-glue.h
-else
-EXTRA_DIST += $(loc_sources)
-endif
CLEANFILES = $(BUILT_SOURCES)
diff --git a/src/main.c b/src/main.c
index 72fa6dc..0f8119e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -11,7 +11,7 @@
* GNU General Public License for more details:
*
* Copyright (C) 2008 - 2009 Novell, Inc.
- * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ * Copyright (C) 2009 - 2011 Red Hat, Inc.
*/
#include <config.h>
@@ -21,8 +21,10 @@
#include <unistd.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
+#include <stdlib.h>
+
#include "mm-manager.h"
-#include "mm-options.h"
+#include "mm-log.h"
#if !defined(MM_DIST_VERSION)
# define MM_DIST_VERSION VERSION
@@ -34,9 +36,9 @@ static void
mm_signal_handler (int signo)
{
if (signo == SIGUSR1)
- mm_options_set_debug (!mm_options_debug ());
+ mm_log_usr1 ();
else if (signo == SIGINT || signo == SIGTERM) {
- g_message ("Caught signal %d, shutting down...", signo);
+ mm_info ("Caught signal %d, shutting down...", signo);
if (loop)
g_main_loop_quit (loop);
else
@@ -60,64 +62,9 @@ setup_signals (void)
}
static void
-log_handler (const gchar *log_domain,
- GLogLevelFlags log_level,
- const gchar *message,
- gpointer ignored)
-{
- int syslog_priority;
-
- switch (log_level) {
- case G_LOG_LEVEL_ERROR:
- syslog_priority = LOG_CRIT;
- break;
-
- case G_LOG_LEVEL_CRITICAL:
- syslog_priority = LOG_ERR;
- break;
-
- case G_LOG_LEVEL_WARNING:
- syslog_priority = LOG_WARNING;
- break;
-
- case G_LOG_LEVEL_MESSAGE:
- syslog_priority = LOG_NOTICE;
- break;
-
- case G_LOG_LEVEL_DEBUG:
- syslog_priority = LOG_DEBUG;
- break;
-
- case G_LOG_LEVEL_INFO:
- default:
- syslog_priority = LOG_INFO;
- break;
- }
-
- syslog (syslog_priority, "%s", message);
-}
-
-
-static void
-logging_setup (void)
-{
- openlog (G_LOG_DOMAIN, LOG_CONS, LOG_DAEMON);
- g_log_set_handler (G_LOG_DOMAIN,
- G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
- log_handler,
- NULL);
-}
-
-static void
-logging_shutdown (void)
-{
- closelog ();
-}
-
-static void
destroy_cb (DBusGProxy *proxy, gpointer user_data)
{
- g_message ("disconnected from the system bus, exiting.");
+ mm_warn ("disconnected from the system bus, exiting.");
g_main_loop_quit (loop);
}
@@ -139,16 +86,16 @@ create_dbus_proxy (DBusGConnection *bus)
G_TYPE_INVALID,
G_TYPE_UINT, &request_name_result,
G_TYPE_INVALID)) {
- g_warning ("Could not acquire the %s service.\n"
- " Message: '%s'", MM_DBUS_SERVICE, err->message);
+ mm_warn ("Could not acquire the %s service.\n"
+ " Message: '%s'", MM_DBUS_SERVICE, err->message);
g_error_free (err);
g_object_unref (proxy);
proxy = NULL;
} else if (request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
- g_warning ("Could not acquire the " MM_DBUS_SERVICE
- " service as it is already taken. Return: %d",
- request_name_result);
+ mm_warn ("Could not acquire the " MM_DBUS_SERVICE
+ " service as it is already taken. Return: %d",
+ request_name_result);
g_object_unref (proxy);
proxy = NULL;
@@ -175,17 +122,49 @@ main (int argc, char *argv[])
DBusGProxy *proxy;
MMManager *manager;
GError *err = NULL;
+ GOptionContext *opt_ctx;
guint id;
+ const char *log_level = NULL, *log_file = NULL;
+ gboolean debug = FALSE, show_ts = FALSE, rel_ts = FALSE;
+
+ GOptionEntry entries[] = {
+ { "debug", 0, 0, G_OPTION_ARG_NONE, &debug, "Output to console rather than syslog", NULL },
+ { "log-level", 0, 0, G_OPTION_ARG_STRING, &log_level, "Log level: one of [ERR, WARN, INFO, DEBUG]", "INFO" },
+ { "log-file", 0, 0, G_OPTION_ARG_STRING, &log_file, "Path to log file", NULL },
+ { "timestamps", 0, 0, G_OPTION_ARG_NONE, &show_ts, "Show timestamps in log output", NULL },
+ { "relative-timestamps", 0, 0, G_OPTION_ARG_NONE, &rel_ts, "Use relative timestamps (from MM start)", NULL },
+ { NULL }
+ };
- mm_options_parse (argc, argv);
g_type_init ();
- setup_signals ();
+ opt_ctx = g_option_context_new (NULL);
+ g_option_context_set_summary (opt_ctx, "DBus system service to communicate with modems.");
+ g_option_context_add_main_entries (opt_ctx, entries, NULL);
- if (!mm_options_debug ())
- logging_setup ();
+ if (!g_option_context_parse (opt_ctx, &argc, &argv, &err)) {
+ g_warning ("%s\n", err->message);
+ g_error_free (err);
+ exit (1);
+ }
- g_message ("ModemManager (version " MM_DIST_VERSION ") starting...");
+ g_option_context_free (opt_ctx);
+
+ if (debug) {
+ log_level = "DEBUG";
+ if (!show_ts && !rel_ts)
+ show_ts = TRUE;
+ }
+
+ if (!mm_log_setup (log_level, log_file, show_ts, rel_ts, &err)) {
+ g_warning ("Failed to set up logging: %s", err->message);
+ g_error_free (err);
+ exit (1);
+ }
+
+ setup_signals ();
+
+ mm_info ("ModemManager (version " MM_DIST_VERSION ") starting...");
bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err);
if (!bus) {
@@ -196,6 +175,17 @@ main (int argc, char *argv[])
return -1;
}
+#ifndef HAVE_DBUS_GLIB_DISABLE_LEGACY_PROP_ACCESS
+#error HAVE_DBUS_GLIB_DISABLE_LEGACY_PROP_ACCESS not defined
+#endif
+
+#if HAVE_DBUS_GLIB_DISABLE_LEGACY_PROP_ACCESS
+ /* Ensure that non-exported properties don't leak out, and that the
+ * introspection 'access' permissions are respected.
+ */
+ dbus_glib_global_set_disable_legacy_property_access ();
+#endif
+
proxy = create_dbus_proxy (bus);
if (!proxy)
return -1;
@@ -224,7 +214,7 @@ main (int argc, char *argv[])
g_object_unref (proxy);
dbus_g_connection_unref (bus);
- logging_shutdown ();
+ mm_log_shutdown ();
return 0;
}
diff --git a/src/mm-at-serial-port.c b/src/mm-at-serial-port.c
index 068450d..30da3a3 100644
--- a/src/mm-at-serial-port.c
+++ b/src/mm-at-serial-port.c
@@ -23,7 +23,7 @@
#include "mm-at-serial-port.h"
#include "mm-errors.h"
-#include "mm-options.h"
+#include "mm-log.h"
G_DEFINE_TYPE (MMAtSerialPort, mm_at_serial_port, MM_TYPE_SERIAL_PORT)
@@ -273,7 +273,6 @@ debug_log (MMSerialPort *port, const char *prefix, const char *buf, gsize len)
{
static GString *debug = NULL;
const char *s;
- GTimeVal tv;
if (!debug)
debug = g_string_sized_new (256);
@@ -290,18 +289,13 @@ debug_log (MMSerialPort *port, const char *prefix, const char *buf, gsize len)
else if (*s == '\n')
g_string_append (debug, "<LF>");
else
- g_string_append_printf (debug, "\\%d", *s);
+ g_string_append_printf (debug, "\\%u", (guint8) (*s & 0xFF));
s++;
}
g_string_append_c (debug, '\'');
- g_get_current_time (&tv);
- g_debug ("<%ld.%ld> (%s): %s",
- tv.tv_sec,
- tv.tv_usec,
- mm_port_get_device (MM_PORT (port)),
- debug->str);
+ mm_dbg ("(%s): %s", mm_port_get_device (MM_PORT (port)), debug->str);
g_string_truncate (debug, 0);
}
diff --git a/src/mm-auth-provider-polkit.c b/src/mm-auth-provider-polkit.c
index c457eaf..2cd4a8a 100644
--- a/src/mm-auth-provider-polkit.c
+++ b/src/mm-auth-provider-polkit.c
@@ -15,6 +15,7 @@
#include <polkit/polkit.h>
+#include <config.h>
#include "mm-auth-request-polkit.h"
#include "mm-auth-provider-polkit.h"
@@ -72,19 +73,39 @@ real_create_request (MMAuthProvider *provider,
/*****************************************************************************/
+/* Fix for polkit 0.97 and later */
+#if !HAVE_POLKIT_AUTHORITY_GET_SYNC
+static inline PolkitAuthority *
+polkit_authority_get_sync (GCancellable *cancellable, GError **error)
+{
+ PolkitAuthority *authority;
+
+ authority = polkit_authority_get ();
+ if (!authority)
+ g_set_error (error, 0, 0, "failed to get the PolicyKit authority");
+ return authority;
+}
+#endif
+
static void
mm_auth_provider_polkit_init (MMAuthProviderPolkit *self)
{
MMAuthProviderPolkitPrivate *priv = MM_AUTH_PROVIDER_POLKIT_GET_PRIVATE (self);
+ GError *error = NULL;
- priv->authority = polkit_authority_get ();
+ priv->authority = polkit_authority_get_sync (NULL, &error);
if (priv->authority) {
priv->auth_changed_id = g_signal_connect (priv->authority,
"changed",
G_CALLBACK (pk_authority_changed_cb),
self);
- } else
- g_warning ("%s: failed to create PolicyKit authority.", __func__);
+ } else {
+ g_warning ("%s: failed to create PolicyKit authority: (%d) %s",
+ __func__,
+ error ? error->code : -1,
+ error && error->message ? error->message : "(unknown)");
+ g_clear_error (&error);
+ }
}
static void
diff --git a/src/mm-auth-provider.h b/src/mm-auth-provider.h
index 26ff340..32082f0 100644
--- a/src/mm-auth-provider.h
+++ b/src/mm-auth-provider.h
@@ -26,6 +26,7 @@
#define MM_AUTHORIZATION_DEVICE_CONTROL "org.freedesktop.ModemManager.Device.Control"
#define MM_AUTHORIZATION_CONTACTS "org.freedesktop.ModemManager.Contacts"
#define MM_AUTHORIZATION_SMS "org.freedesktop.ModemManager.SMS"
+#define MM_AUTHORIZATION_USSD "org.freedesktop.ModemManager.USSD"
#define MM_AUTHORIZATION_LOCATION "org.freedesktop.ModemManager.Location"
/******************/
diff --git a/src/mm-charsets.c b/src/mm-charsets.c
index c75c3a9..d2b9a66 100644
--- a/src/mm-charsets.c
+++ b/src/mm-charsets.c
@@ -109,7 +109,7 @@ charset_iconv_from (MMModemCharset charset)
gboolean
mm_modem_charset_byte_array_append (GByteArray *array,
- const char *string,
+ const char *utf8,
gboolean quoted,
MMModemCharset charset)
{
@@ -119,22 +119,16 @@ mm_modem_charset_byte_array_append (GByteArray *array,
gsize written = 0;
g_return_val_if_fail (array != NULL, FALSE);
- g_return_val_if_fail (string != NULL, FALSE);
+ g_return_val_if_fail (utf8 != NULL, FALSE);
iconv_to = charset_iconv_to (charset);
g_return_val_if_fail (iconv_to != NULL, FALSE);
- converted = g_convert (string,
- g_utf8_strlen (string, -1),
- iconv_to,
- "UTF-8",
- NULL,
- &written,
- &error);
+ converted = g_convert (utf8, -1, iconv_to, "UTF-8", NULL, &written, &error);
if (!converted) {
if (error) {
g_warning ("%s: failed to convert '%s' to %s character set: (%d) %s",
- __func__, string, iconv_to,
+ __func__, utf8, iconv_to,
error->code, error->message);
g_error_free (error);
}
@@ -154,9 +148,10 @@ mm_modem_charset_byte_array_append (GByteArray *array,
char *
mm_modem_charset_hex_to_utf8 (const char *src, MMModemCharset charset)
{
- char *unconverted;
+ char *unconverted, *converted;
const char *iconv_from;
gsize unconverted_len = 0;
+ GError *error = NULL;
g_return_val_if_fail (src != NULL, NULL);
g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL);
@@ -170,6 +165,340 @@ mm_modem_charset_hex_to_utf8 (const char *src, MMModemCharset charset)
if (charset == MM_MODEM_CHARSET_UTF8 || charset == MM_MODEM_CHARSET_IRA)
return unconverted;
- return g_convert (unconverted, unconverted_len, "UTF-8//TRANSLIT", iconv_from, NULL, NULL, NULL);
+ converted = g_convert (unconverted, unconverted_len,
+ "UTF-8//TRANSLIT", iconv_from,
+ NULL, NULL, &error);
+ if (!converted || error) {
+ g_clear_error (&error);
+ g_free (unconverted);
+ converted = NULL;
+ }
+
+ return converted;
+}
+
+
+/* GSM 03.38 encoding conversion stuff */
+
+#define GSM_DEF_ALPHABET_SIZE 128
+#define GSM_EXT_ALPHABET_SIZE 10
+
+typedef struct GsmUtf8Mapping {
+ gchar chars[3];
+ guint8 len;
+ guint8 gsm; /* only used for extended GSM charset */
+} GsmUtf8Mapping;
+
+#define ONE(a) { {a, 0x00, 0x00}, 1, 0 }
+#define TWO(a, b) { {a, b, 0x00}, 2, 0 }
+
+/**
+ * gsm_def_utf8_alphabet:
+ *
+ * Mapping from GSM default alphabet to UTF-8.
+ *
+ * ETSI GSM 03.38, version 6.0.1, section 6.2.1; Default alphabet. Mapping to UCS-2.
+ * Mapping according to http://unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT
+ */
+static const GsmUtf8Mapping gsm_def_utf8_alphabet[GSM_DEF_ALPHABET_SIZE] = {
+ /* @ £ $ ¥ */
+ ONE(0x40), TWO(0xc2, 0xa3), ONE(0x24), TWO(0xc2, 0xa5),
+ /* è é ù ì */
+ TWO(0xc3, 0xa8), TWO(0xc3, 0xa9), TWO(0xc3, 0xb9), TWO(0xc3, 0xac),
+ /* ò Ç \n Ø */
+ TWO(0xc3, 0xb2), TWO(0xc3, 0x87), ONE(0x0a), TWO(0xc3, 0x98),
+ /* ø \r Å å */
+ TWO(0xc3, 0xb8), ONE(0x0d), TWO(0xc3, 0x85), TWO(0xc3, 0xa5),
+ /* Δ _ Φ Γ */
+ TWO(0xce, 0x94), ONE(0x5f), TWO(0xce, 0xa6), TWO(0xce, 0x93),
+ /* Λ Ω Π Ψ */
+ TWO(0xce, 0x9b), TWO(0xce, 0xa9), TWO(0xce, 0xa0), TWO(0xce, 0xa8),
+ /* Σ Θ Ξ Escape Code */
+ TWO(0xce, 0xa3), TWO(0xce, 0x98), TWO(0xce, 0x9e), ONE(0xa0),
+ /* Æ æ ß É */
+ TWO(0xc3, 0x86), TWO(0xc3, 0xa6), TWO(0xc3, 0x9f), TWO(0xc3, 0x89),
+ /* ' ' ! " # */
+ ONE(0x20), ONE(0x21), ONE(0x22), ONE(0x23),
+ /* ¤ % & ' */
+ TWO(0xc2, 0xa4), ONE(0x25), ONE(0x26), ONE(0x27),
+ /* ( ) * + */
+ ONE(0x28), ONE(0x29), ONE(0x2a), ONE(0x2b),
+ /* , - . / */
+ ONE(0x2c), ONE(0x2d), ONE(0x2e), ONE(0x2f),
+ /* 0 1 2 3 */
+ ONE(0x30), ONE(0x31), ONE(0x32), ONE(0x33),
+ /* 4 5 6 7 */
+ ONE(0x34), ONE(0x35), ONE(0x36), ONE(0x37),
+ /* 8 9 : ; */
+ ONE(0x38), ONE(0x39), ONE(0x3a), ONE(0x3b),
+ /* < = > ? */
+ ONE(0x3c), ONE(0x3d), ONE(0x3e), ONE(0x3f),
+ /* ¡ A B C */
+ TWO(0xc2, 0xa1), ONE(0x41), ONE(0x42), ONE(0x43),
+ /* D E F G */
+ ONE(0x44), ONE(0x45), ONE(0x46), ONE(0x47),
+ /* H I J K */
+ ONE(0x48), ONE(0x49), ONE(0x4a), ONE(0x4b),
+ /* L M N O */
+ ONE(0x4c), ONE(0x4d), ONE(0x4e), ONE(0x4f),
+ /* P Q R S */
+ ONE(0x50), ONE(0x51), ONE(0x52), ONE(0x53),
+ /* T U V W */
+ ONE(0x54), ONE(0x55), ONE(0x56), ONE(0x57),
+ /* X Y Z Ä */
+ ONE(0x58), ONE(0x59), ONE(0x5a), TWO(0xc3, 0x84),
+ /* Ö Ñ Ü § */
+ TWO(0xc3, 0x96), TWO(0xc3, 0x91), TWO(0xc3, 0x9c), TWO(0xc2, 0xa7),
+ /* ¿ a b c */
+ TWO(0xc2, 0xbf), ONE(0x61), ONE(0x62), ONE(0x63),
+ /* d e f g */
+ ONE(0x64), ONE(0x65), ONE(0x66), ONE(0x67),
+ /* h i j k */
+ ONE(0x68), ONE(0x69), ONE(0x6a), ONE(0x6b),
+ /* l m n o */
+ ONE(0x6c), ONE(0x6d), ONE(0x6e), ONE(0x6f),
+ /* p q r s */
+ ONE(0x70), ONE(0x71), ONE(0x72), ONE(0x73),
+ /* t u v w */
+ ONE(0x74), ONE(0x75), ONE(0x76), ONE(0x77),
+ /* x y z ä */
+ ONE(0x78), ONE(0x79), ONE(0x7a), TWO(0xc3, 0xa4),
+ /* ö ñ ü à */
+ TWO(0xc3, 0xb6), TWO(0xc3, 0xb1), TWO(0xc3, 0xbc), TWO(0xc3, 0xa0)
+};
+
+static guint8
+gsm_def_char_to_utf8 (const guint8 gsm, guint8 out_utf8[2])
+{
+ g_return_val_if_fail (gsm < GSM_DEF_ALPHABET_SIZE, 0);
+ memcpy (&out_utf8[0], &gsm_def_utf8_alphabet[gsm].chars[0], gsm_def_utf8_alphabet[gsm].len);
+ return gsm_def_utf8_alphabet[gsm].len;
+}
+
+static gboolean
+utf8_to_gsm_def_char (const char *utf8, guint32 len, guint8 *out_gsm)
+{
+ int i;
+
+ if (len > 0 && len < 4) {
+ for (i = 0; i < GSM_DEF_ALPHABET_SIZE; i++) {
+ if (gsm_def_utf8_alphabet[i].len == len) {
+ if (memcmp (&gsm_def_utf8_alphabet[i].chars[0], utf8, len) == 0) {
+ *out_gsm = i;
+ return TRUE;
+ }
+ }
+ }
+ }
+ return FALSE;
+}
+
+
+#define EONE(a, g) { {a, 0x00, 0x00}, 1, g }
+#define ETHR(a, b, c, g) { {a, b, c}, 3, g }
+
+/**
+ * gsm_ext_utf8_alphabet:
+ *
+ * Mapping from GSM extended alphabet to UTF-8.
+ *
+ */
+static const GsmUtf8Mapping gsm_ext_utf8_alphabet[GSM_EXT_ALPHABET_SIZE] = {
+ /* form feed ^ { } */
+ EONE(0x0c, 0x0a), EONE(0x5e, 0x14), EONE(0x7b, 0x28), EONE(0x7d, 0x29),
+ /* \ [ ~ ] */
+ EONE(0x5c, 0x2f), EONE(0x5b, 0x3c), EONE(0x7e, 0x3d), EONE(0x5d, 0x3e),
+ /* | € */
+ EONE(0x7c, 0x40), ETHR(0xe2, 0x82, 0xac, 0x65)
+};
+
+#define GSM_ESCAPE_CHAR 0x1b
+
+static guint8
+gsm_ext_char_to_utf8 (const guint8 gsm, guint8 out_utf8[3])
+{
+ int i;
+
+ for (i = 0; i < GSM_EXT_ALPHABET_SIZE; i++) {
+ if (gsm == gsm_ext_utf8_alphabet[i].gsm) {
+ memcpy (&out_utf8[0], &gsm_ext_utf8_alphabet[i].chars[0], gsm_ext_utf8_alphabet[i].len);
+ return gsm_ext_utf8_alphabet[i].len;
+ }
+ }
+ return 0;
+}
+
+static gboolean
+utf8_to_gsm_ext_char (const char *utf8, guint32 len, guint8 *out_gsm)
+{
+ int i;
+
+ if (len > 0 && len < 4) {
+ for (i = 0; i < GSM_EXT_ALPHABET_SIZE; i++) {
+ if (gsm_ext_utf8_alphabet[i].len == len) {
+ if (memcmp (&gsm_ext_utf8_alphabet[i].chars[0], utf8, len) == 0) {
+ *out_gsm = gsm_ext_utf8_alphabet[i].gsm;
+ return TRUE;
+ }
+ }
+ }
+ }
+ return FALSE;
+}
+
+guint8 *
+mm_charset_gsm_unpacked_to_utf8 (const guint8 *gsm, guint32 len)
+{
+ int i;
+ GByteArray *utf8;
+
+ g_return_val_if_fail (gsm != NULL, NULL);
+ g_return_val_if_fail (len < 4096, NULL);
+
+ /* worst case initial length */
+ utf8 = g_byte_array_sized_new (len * 2 + 1);
+
+ for (i = 0; i < len; i++) {
+ guint8 uchars[4];
+ guint8 ulen;
+
+ if (gsm[i] == GSM_ESCAPE_CHAR) {
+ /* Extended alphabet, decode next char */
+ ulen = gsm_ext_char_to_utf8 (gsm[i+1], uchars);
+ if (ulen)
+ i += 1;
+ } else {
+ /* Default alphabet */
+ ulen = gsm_def_char_to_utf8 (gsm[i], uchars);
+ }
+
+ if (ulen)
+ g_byte_array_append (utf8, &uchars[0], ulen);
+ else
+ g_byte_array_append (utf8, (guint8 *) "?", 1);
+ }
+
+ g_byte_array_append (utf8, (guint8 *) "\0", 1); /* NULL terminator */
+ return g_byte_array_free (utf8, FALSE);
+}
+
+guint8 *
+mm_charset_utf8_to_unpacked_gsm (const char *utf8, guint32 *out_len)
+{
+ GByteArray *gsm;
+ const char *c = utf8, *next = c;
+ static const guint8 gesc = GSM_ESCAPE_CHAR;
+ int i = 0;
+
+ g_return_val_if_fail (utf8 != NULL, NULL);
+ g_return_val_if_fail (out_len != NULL, NULL);
+ g_return_val_if_fail (g_utf8_validate (utf8, -1, NULL), NULL);
+
+ /* worst case initial length */
+ gsm = g_byte_array_sized_new (g_utf8_strlen (utf8, -1) * 2 + 1);
+
+ if (*utf8 == 0x00) {
+ /* Zero-length string */
+ g_byte_array_append (gsm, (guint8 *) "\0", 1);
+ *out_len = 0;
+ return g_byte_array_free (gsm, FALSE);
+ }
+
+ while (next && *next) {
+ guint8 gch = 0x3f; /* 0x3f == '?' */
+
+ next = g_utf8_next_char (c);
+
+ /* Try escaped chars first, then default alphabet */
+ if (utf8_to_gsm_ext_char (c, next - c, &gch)) {
+ /* Add the escape char */
+ g_byte_array_append (gsm, &gesc, 1);
+ g_byte_array_append (gsm, &gch, 1);
+ } else if (utf8_to_gsm_def_char (c, next - c, &gch))
+ g_byte_array_append (gsm, &gch, 1);
+
+ c = next;
+ i++;
+ }
+
+ *out_len = gsm->len;
+ return g_byte_array_free (gsm, FALSE);
+}
+
+guint8 *
+gsm_unpack (const guint8 *gsm,
+ guint32 gsm_len,
+ guint8 start_offset, /* in _bits_ */
+ guint32 *out_unpacked_len)
+{
+ GByteArray *unpacked;
+ int i, nchars;
+
+ nchars = ((gsm_len * 8) - start_offset) / 7;
+ unpacked = g_byte_array_sized_new (nchars + 1);
+
+ for (i = 0; i < nchars; i++) {
+ guint8 bits_here, bits_in_next, octet, offset, c;
+ guint32 start_bit;
+
+ start_bit = start_offset + (i * 7); /* Overall bit offset of char in buffer */
+ offset = start_bit % 8; /* Offset to start of char in this byte */
+ bits_here = offset ? (8 - offset) : 7;
+ bits_in_next = 7 - bits_here;
+
+ /* Grab bits in the current byte */
+ octet = gsm[start_bit / 8];
+ c = (octet >> offset) & (0xFF >> (8 - bits_here));
+
+ /* Grab any bits that spilled over to next byte */
+ if (bits_in_next) {
+ octet = gsm[(start_bit / 8) + 1];
+ c |= (octet & (0xFF >> (8 - bits_in_next))) << bits_here;
+ }
+ g_byte_array_append (unpacked, &c, 1);
+ }
+
+ *out_unpacked_len = unpacked->len;
+ return g_byte_array_free (unpacked, FALSE);
+}
+
+guint8 *
+gsm_pack (const guint8 *src,
+ guint32 src_len,
+ guint8 start_offset,
+ guint32 *out_packed_len)
+{
+ GByteArray *packed;
+ guint8 c, add_last = 0;
+ int i;
+
+ packed = g_byte_array_sized_new (src_len);
+
+ for (i = 0, c = 0; i < src_len; i++) {
+ guint8 bits_here, offset;
+ guint32 start_bit;
+
+ start_bit = start_offset + (i * 7); /* Overall bit offset of char in buffer */
+ offset = start_bit % 8; /* Offset to start of char in this byte */
+ bits_here = offset ? (8 - offset) : 7;
+
+ c |= (src[i] & 0x7F) << offset;
+ if (offset) {
+ /* Add this packed byte */
+ g_byte_array_append (packed, &c, 1);
+ c = add_last = 0;
+ }
+
+ /* Pack the rest of this char into the next byte */
+ if (bits_here != 7) {
+ c = (src[i] & 0x7F) >> bits_here;
+ add_last = 1;
+ }
+ }
+ if (add_last)
+ g_byte_array_append (packed, &c, 1);
+
+ *out_packed_len = packed->len;
+ return g_byte_array_free (packed, FALSE);
}
diff --git a/src/mm-charsets.h b/src/mm-charsets.h
index 5fa3406..661052d 100644
--- a/src/mm-charsets.h
+++ b/src/mm-charsets.h
@@ -39,7 +39,7 @@ MMModemCharset mm_modem_charset_from_string (const char *string);
* UTF-8 encoded.
*/
gboolean mm_modem_charset_byte_array_append (GByteArray *array,
- const char *string,
+ const char *utf8,
gboolean quoted,
MMModemCharset charset);
@@ -48,5 +48,19 @@ gboolean mm_modem_charset_byte_array_append (GByteArray *array,
*/
char *mm_modem_charset_hex_to_utf8 (const char *src, MMModemCharset charset);
+guint8 *mm_charset_utf8_to_unpacked_gsm (const char *utf8, guint32 *out_len);
+
+guint8 *mm_charset_gsm_unpacked_to_utf8 (const guint8 *gsm, guint32 len);
+
+guint8 *gsm_unpack (const guint8 *gsm,
+ guint32 gsm_len,
+ guint8 start_offset, /* in bits */
+ guint32 *out_unpacked_len);
+
+guint8 *gsm_pack (const guint8 *src,
+ guint32 src_len,
+ guint8 start_offset, /* in bits */
+ guint32 *out_packed_len);
+
#endif /* MM_CHARSETS_H */
diff --git a/src/mm-generic-cdma.c b/src/mm-generic-cdma.c
index 9fe897f..0a95e7b 100644
--- a/src/mm-generic-cdma.c
+++ b/src/mm-generic-cdma.c
@@ -30,6 +30,7 @@
#include "mm-serial-parsers.h"
#include "mm-modem-helpers.h"
#include "libqcdm/src/commands.h"
+#include "mm-log.h"
#define MM_GENERIC_CDMA_PREV_STATE_TAG "prev-state"
@@ -68,6 +69,8 @@ typedef struct {
guint poll_id;
+ char *meid;
+
MMModemCdmaRegistrationState cdma_1x_reg_state;
MMModemCdmaRegistrationState evdo_reg_state;
@@ -95,7 +98,9 @@ mm_generic_cdma_new (const char *device,
const char *driver,
const char *plugin,
gboolean evdo_rev0,
- gboolean evdo_revA)
+ gboolean evdo_revA,
+ guint vendor,
+ guint product)
{
g_return_val_if_fail (device != NULL, NULL);
g_return_val_if_fail (driver != NULL, NULL);
@@ -107,6 +112,8 @@ mm_generic_cdma_new (const char *device,
MM_MODEM_PLUGIN, plugin,
MM_GENERIC_CDMA_EVDO_REV0, evdo_rev0,
MM_GENERIC_CDMA_EVDO_REVA, evdo_revA,
+ MM_MODEM_HW_VID, vendor,
+ MM_MODEM_HW_PID, product,
NULL));
}
@@ -162,6 +169,47 @@ initial_esn_check (MMGenericCdma *self)
}
}
+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_CDMA_GET_PRIVATE (modem)->primary));
+}
+
+static void
+initial_info_check (MMGenericCdma *self)
+{
+ GError *error = NULL;
+ MMGenericCdmaPrivate *priv;
+
+ g_return_if_fail (MM_IS_GENERIC_CDMA (self));
+ priv = MM_GENERIC_CDMA_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)
{
@@ -215,6 +263,9 @@ mm_generic_cdma_grab_port (MMGenericCdma *self,
g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE);
}
+ /* Get the modem's general info */
+ initial_info_check (self);
+
/* Get modem's ESN number */
initial_esn_check (self);
@@ -315,6 +366,15 @@ mm_generic_cdma_get_best_at_port (MMGenericCdma *self, GError **error)
return priv->secondary;
}
+MMQcdmSerialPort *
+mm_generic_cdma_get_best_qcdm_port (MMGenericCdma *self, GError **error)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), NULL);
+
+ return MM_GENERIC_CDMA_GET_PRIVATE (self)->qcdm;
+}
+
/*****************************************************************************/
void
@@ -552,25 +612,6 @@ out:
}
static void
-enable_error_reporting_done (MMAtSerialPort *port,
- GString *response,
- GError *error,
- gpointer user_data)
-{
- MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
-
- /* Just ignore errors, see comment in init_done() */
- if (error)
- g_warning ("Your CDMA modem does not support +CMEE command");
-
- if (MM_GENERIC_CDMA_GET_CLASS (self)->post_enable)
- MM_GENERIC_CDMA_GET_CLASS (self)->post_enable (self, enable_all_done, info);
- else
- enable_all_done (MM_MODEM (self), NULL, info);
-}
-
-static void
init_done (MMAtSerialPort *port,
GString *response,
GError *error,
@@ -586,12 +627,17 @@ init_done (MMAtSerialPort *port,
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
} else {
- /* Try to enable better error reporting. My experience so far indicates
- there's some CDMA modems that does not support that.
- FIXME: It's mandatory by spec, so it really shouldn't be optional. Figure
- out which CDMA modems have problems with it and implement plugin for them.
- */
- mm_at_serial_port_queue_command (port, "+CMEE=1", 3, enable_error_reporting_done, user_data);
+ MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
+
+ /* Try enabling better error reporting on CDMA devices, but few
+ * actually support +CMEE as it's more of a GSM command.
+ */
+ mm_at_serial_port_queue_command (port, "+CMEE=1", 3, NULL, NULL);
+
+ if (MM_GENERIC_CDMA_GET_CLASS (self)->post_enable)
+ MM_GENERIC_CDMA_GET_CLASS (self)->post_enable (self, enable_all_done, info);
+ else
+ enable_all_done (MM_MODEM (self), NULL, info);
}
}
@@ -1002,7 +1048,7 @@ get_signal_quality (MMModemCdma *modem,
at_port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error);
if (!at_port && !priv->qcdm) {
- g_message ("Returning saved signal quality %d", priv->cdma1x_quality);
+ mm_dbg ("Returning saved signal quality %d", priv->cdma1x_quality);
mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->cdma1x_quality), NULL);
mm_callback_info_schedule (info);
return;
@@ -1481,15 +1527,22 @@ reg_query_speri_done (MMAtSerialPort *port,
if (!p || !mm_cdma_parse_eri (p, &roam, NULL, NULL))
goto done;
- /* Change the 1x and EVDO registration states to roaming if they were
- * anything other than UNKNOWN.
- */
if (roam) {
+ /* Change the 1x and EVDO registration states to roaming if they were
+ * anything other than UNKNOWN.
+ */
if (mm_generic_cdma_query_reg_state_get_callback_1x_state (info))
mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING);
if (mm_generic_cdma_query_reg_state_get_callback_evdo_state (info))
mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING);
+ } else {
+ /* Change 1x and/or EVDO registration state to home if home/roaming wasn't previously known */
+ if (mm_generic_cdma_query_reg_state_get_callback_1x_state (info) == MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED)
+ mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
+
+ if (mm_generic_cdma_query_reg_state_get_callback_evdo_state (info) == MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED)
+ mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
}
done:
@@ -1541,6 +1594,12 @@ real_query_registration_state (MMGenericCdma *self,
port = mm_generic_cdma_get_best_at_port (self, &info->error);
if (!port) {
+ /* If we can't get an AT port, but less specific registration checks
+ * were successful, just use that and don't return an error.
+ */
+ if ( cur_cdma_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN
+ || cur_evdo_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ g_clear_error (&info->error);
mm_callback_info_schedule (info);
return;
}
@@ -1658,58 +1717,79 @@ error:
}
static void
-reg_cmstate_cb (MMQcdmSerialPort *port,
- GByteArray *response,
- GError *error,
- gpointer user_data)
+reg_hdrstate_cb (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ gpointer user_data)
{
MMCallbackInfo *info = user_data;
- MMAtSerialPort *at_port;
- QCDMResult *result;
- guint32 opmode = 0, sysmode = 0;
+ QCDMResult *result = NULL;
+ guint32 sysmode;
MMModemCdmaRegistrationState cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ MMAtSerialPort *at_port;
+ gboolean evdo_registered = FALSE;
if (error)
goto error;
- /* Parse the response */
- result = qcdm_cmd_cm_subsys_state_info_result ((const char *) response->data, response->len, &info->error);
- if (!result)
- goto error;
+ sysmode = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "sysmode"));
- qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &opmode);
- qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, &sysmode);
- qcdm_result_unref (result);
+ /* Get HDR subsystem state to determine EVDO registration when in 1X mode */
+ result = qcdm_cmd_hdr_subsys_state_info_result ((const char *) response->data,
+ response->len,
+ NULL);
+ if (result) {
+ guint8 session_state = QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_CLOSED;
+ guint8 almp_state = QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_INACTIVE;
+ guint8 hybrid_mode = 0;
- if (opmode == QCDM_CMD_CM_SUBSYS_STATE_INFO_OPERATING_MODE_ONLINE) {
- switch (sysmode) {
- case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_CDMA:
- cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
- break;
- case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_HDR:
- evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
- break;
- case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_AMPS:
- case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_NO_SERVICE:
- case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_WCDMA:
- default:
- break;
- }
+ if ( qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE, &session_state)
+ && qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE, &almp_state)
+ && qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_HDR_HYBRID_MODE, &hybrid_mode)) {
- if (cdma_state || evdo_state) {
- /* Device is registered to something; see if the subclass has a
- * better idea of whether we're roaming or not and what the
- * access technology is.
+ /* EVDO state is registered if the HDR subsystem is registered, and
+ * we're in hybrid mode, and the Call Manager system mode is
+ * CDMA.
*/
- if (MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state) {
- MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state (MM_GENERIC_CDMA (info->modem),
- cdma_state,
- evdo_state,
- subclass_reg_query_done,
- info);
- return;
- }
+ if ( hybrid_mode
+ && session_state == QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_OPEN
+ && ( almp_state == QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_IDLE
+ || almp_state == QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_CONNECTED))
+ evdo_registered = TRUE;
+ }
+
+ qcdm_result_unref (result);
+ }
+
+ switch (sysmode) {
+ case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_CDMA:
+ cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+ if (evdo_registered)
+ evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+ break;
+ case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_HDR:
+ evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+ break;
+ case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_AMPS:
+ case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_NO_SERVICE:
+ case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_WCDMA:
+ default:
+ break;
+ }
+
+ if (cdma_state || evdo_state) {
+ /* Device is registered to something; see if the subclass has a
+ * better idea of whether we're roaming or not and what the
+ * access technology is.
+ */
+ if (MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state) {
+ MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state (MM_GENERIC_CDMA (info->modem),
+ cdma_state,
+ evdo_state,
+ subclass_reg_query_done,
+ info);
+ return;
}
}
@@ -1728,6 +1808,59 @@ error:
}
static void
+reg_cmstate_cb (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+ MMAtSerialPort *at_port = NULL;
+ QCDMResult *result = NULL;
+ guint32 opmode = 0, sysmode = 0;
+ GError *qcdm_error = NULL;
+
+ /* Parse the response */
+ if (!error)
+ result = qcdm_cmd_cm_subsys_state_info_result ((const char *) response->data, response->len, &qcdm_error);
+
+ if (!result) {
+ /* If there was some error, fall back to use +CAD like we did before QCDM */
+ if (info->modem)
+ at_port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (info->modem), &info->error);
+ else
+ info->error = g_error_copy (qcdm_error);
+
+ if (at_port)
+ mm_at_serial_port_queue_command (at_port, "+CAD?", 3, get_analog_digital_done, info);
+ else
+ mm_callback_info_schedule (info);
+ g_clear_error (&qcdm_error);
+ return;
+ }
+
+ qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &opmode);
+ qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, &sysmode);
+ qcdm_result_unref (result);
+
+ if (opmode == QCDM_CMD_CM_SUBSYS_STATE_INFO_OPERATING_MODE_ONLINE) {
+ GByteArray *hdrstate;
+
+ mm_callback_info_set_data (info, "sysmode", GUINT_TO_POINTER (sysmode), NULL);
+
+ /* Get HDR subsystem state */
+ hdrstate = g_byte_array_sized_new (25);
+ hdrstate->len = qcdm_cmd_hdr_subsys_state_info_new ((char *) hdrstate->data, 25, NULL);
+ g_assert (hdrstate->len);
+ mm_qcdm_serial_port_queue_command (port, hdrstate, 3, reg_hdrstate_cb, info);
+ } else {
+ /* No service */
+ set_callback_1x_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+ set_callback_evdo_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+ mm_callback_info_schedule (info);
+ }
+}
+
+static void
get_registration_state (MMModemCdma *modem,
MMModemCdmaRegistrationStateFn callback,
gpointer user_data)
@@ -1744,13 +1877,14 @@ get_registration_state (MMModemCdma *modem,
port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error);
if (!port && !priv->qcdm) {
- g_message ("Returning saved registration states: 1x: %d EVDO: %d",
- priv->cdma_1x_reg_state, priv->evdo_reg_state);
+ mm_dbg ("Returning saved registration states: 1x: %d EVDO: %d",
+ priv->cdma_1x_reg_state, priv->evdo_reg_state);
mm_generic_cdma_query_reg_state_set_callback_1x_state (info, priv->cdma_1x_reg_state);
mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, priv->evdo_reg_state);
mm_callback_info_schedule (info);
return;
}
+ g_clear_error (&info->error);
/* Use QCDM for Call Manager state or HDR state before trying CAD, since
* CAD doesn't always reflect the state of the HDR radio's registration
@@ -2155,6 +2289,9 @@ get_property (GObject *object, guint prop_id,
case MM_MODEM_PROP_TYPE:
g_value_set_uint (value, MM_MODEM_TYPE_CDMA);
break;
+ case MM_MODEM_CDMA_PROP_MEID:
+ g_value_set_string (value, priv->meid);
+ break;
case PROP_EVDO_REV0:
g_value_set_boolean (value, priv->evdo_rev0);
break;
@@ -2207,6 +2344,10 @@ mm_generic_cdma_class_init (MMGenericCdmaClass *klass)
MM_MODEM_PROP_TYPE,
MM_MODEM_TYPE);
+ g_object_class_override_property (object_class,
+ MM_MODEM_CDMA_PROP_MEID,
+ MM_MODEM_CDMA_MEID);
+
g_object_class_install_property (object_class, PROP_EVDO_REV0,
g_param_spec_boolean (MM_GENERIC_CDMA_EVDO_REV0,
"EVDO rev0",
diff --git a/src/mm-generic-cdma.h b/src/mm-generic-cdma.h
index e4f9e57..350c58e 100644
--- a/src/mm-generic-cdma.h
+++ b/src/mm-generic-cdma.h
@@ -21,6 +21,7 @@
#include "mm-modem-base.h"
#include "mm-modem-cdma.h"
#include "mm-at-serial-port.h"
+#include "mm-qcdm-serial-port.h"
#include "mm-callback-info.h"
#define MM_TYPE_GENERIC_CDMA (mm_generic_cdma_get_type ())
@@ -87,7 +88,9 @@ MMModem *mm_generic_cdma_new (const char *device,
const char *driver,
const char *plugin,
gboolean evdo_rev0,
- gboolean evdo_revA);
+ gboolean evdo_revA,
+ guint vendor,
+ guint product);
/* Private, for subclasses */
@@ -103,6 +106,9 @@ MMAtSerialPort *mm_generic_cdma_get_at_port (MMGenericCdma *modem, MMPortType pt
MMAtSerialPort *mm_generic_cdma_get_best_at_port (MMGenericCdma *modem,
GError **error);
+MMQcdmSerialPort *mm_generic_cdma_get_best_qcdm_port (MMGenericCdma *modem,
+ GError **error);
+
void mm_generic_cdma_update_cdma1x_quality (MMGenericCdma *self, guint32 quality);
void mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality);
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, &reg_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,
diff --git a/src/mm-generic-gsm.h b/src/mm-generic-gsm.h
index de9dc89..5712660 100644
--- a/src/mm-generic-gsm.h
+++ b/src/mm-generic-gsm.h
@@ -17,6 +17,8 @@
#ifndef MM_GENERIC_GSM_H
#define MM_GENERIC_GSM_H
+#include <config.h>
+
#include "mm-modem-gsm.h"
#include "mm-modem-gsm-network.h"
#include "mm-modem-base.h"
@@ -46,7 +48,15 @@ typedef enum {
MM_GENERIC_GSM_PROP_SUPPORTED_MODES,
MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL,
MM_GENERIC_GSM_PROP_ALLOWED_MODE,
- MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY
+ MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY,
+ MM_GENERIC_GSM_PROP_LOC_CAPABILITIES,
+ MM_GENERIC_GSM_PROP_LOC_ENABLED,
+ MM_GENERIC_GSM_PROP_LOC_SIGNAL,
+ MM_GENERIC_GSM_PROP_LOC_LOCATION,
+ MM_GENERIC_GSM_PROP_SIM_IDENTIFIER,
+ MM_GENERIC_GSM_PROP_USSD_STATE,
+ MM_GENERIC_GSM_PROP_USSD_NETWORK_REQUEST,
+ MM_GENERIC_GSM_PROP_USSD_NETWORK_NOTIFICATION,
} MMGenericGsmProp;
typedef enum {
@@ -110,13 +120,30 @@ typedef struct {
void (*get_access_technology) (MMGenericGsm *self,
MMModemUIntFn callback,
gpointer user_data);
+
+ /* Called by the generic class to get additional Location capabilities that
+ * subclasses may implement. The MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI
+ * capabilities is automatically provided by the generic class, and
+ * subclasses should return a bitfield of additional location capabilities
+ * they support in the callback here.
+ */
+ void (*loc_get_capabilities) (MMGenericGsm *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+ /* Called by the generic class to retrieve the SIM's ICCID */
+ void (*get_sim_iccid) (MMGenericGsm *self,
+ MMModemStringFn callback,
+ gpointer user_data);
} MMGenericGsmClass;
GType mm_generic_gsm_get_type (void);
MMModem *mm_generic_gsm_new (const char *device,
const char *driver,
- const char *plugin);
+ const char *plugin,
+ guint vendor,
+ guint product);
/* Private, for subclasses */
diff --git a/src/mm-log.c b/src/mm-log.c
new file mode 100644
index 0000000..bcf806a
--- /dev/null
+++ b/src/mm-log.c
@@ -0,0 +1,233 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ */
+
+#define _GNU_SOURCE
+#include <config.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mm-log.h"
+
+enum {
+ TS_FLAG_NONE = 0,
+ TS_FLAG_WALL,
+ TS_FLAG_REL
+};
+
+static gboolean ts_flags = TS_FLAG_NONE;
+static guint32 log_level = LOGL_INFO | LOGL_WARN | LOGL_ERR;
+static GTimeVal rel_start = { 0, 0 };
+static int logfd = -1;
+
+typedef struct {
+ guint32 num;
+ const char *name;
+} LogDesc;
+
+static const LogDesc level_descs[] = {
+ { LOGL_ERR, "ERR" },
+ { LOGL_WARN | LOGL_ERR, "WARN" },
+ { LOGL_INFO | LOGL_WARN | LOGL_ERR, "INFO" },
+ { LOGL_DEBUG | LOGL_INFO | LOGL_WARN | LOGL_ERR, "DEBUG" },
+ { 0, NULL }
+};
+
+void
+_mm_log (const char *loc,
+ const char *func,
+ guint32 level,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ char *msg;
+ GTimeVal tv;
+ char tsbuf[100] = { 0 };
+ char msgbuf[512] = { 0 };
+ int syslog_priority = LOG_INFO;
+ const char *prefix = NULL;
+ ssize_t ign;
+
+ if (!(log_level & level))
+ return;
+
+ va_start (args, fmt);
+ msg = g_strdup_vprintf (fmt, args);
+ va_end (args);
+
+ if (ts_flags == TS_FLAG_WALL) {
+ g_get_current_time (&tv);
+ snprintf (&tsbuf[0], sizeof (tsbuf), " [%09ld.%06ld]", tv.tv_sec, tv.tv_usec);
+ } else if (ts_flags == TS_FLAG_REL) {
+ time_t secs;
+ suseconds_t usecs;
+
+ g_get_current_time (&tv);
+ secs = tv.tv_sec - rel_start.tv_sec;
+ usecs = tv.tv_usec - rel_start.tv_usec;
+ if (usecs < 0) {
+ secs--;
+ usecs += 1000000;
+ }
+
+ snprintf (&tsbuf[0], sizeof (tsbuf), " [%06ld.%06ld]", secs, usecs);
+ }
+
+ if ((log_level & LOGL_DEBUG) && (level == LOGL_DEBUG))
+ prefix = "debug";
+ else if ((log_level & LOGL_INFO) && (level == LOGL_INFO))
+ prefix = "info";
+ else if ((log_level & LOGL_WARN) && (level == LOGL_WARN)) {
+ prefix = "warn";
+ syslog_priority = LOG_WARNING;
+ } else if ((log_level & LOGL_ERR) && (level == LOGL_ERR)) {
+ prefix = "err";
+ syslog_priority = LOG_ERR;
+ } else
+ g_warn_if_reached ();
+
+ if (prefix) {
+ if (log_level & LOGL_DEBUG)
+ snprintf (msgbuf, sizeof (msgbuf), "<%s>%s [%s] %s(): %s\n", prefix, tsbuf, loc, func, msg);
+ else
+ snprintf (msgbuf, sizeof (msgbuf), "<%s>%s %s\n", prefix, tsbuf, msg);
+
+ if (logfd < 0)
+ syslog (syslog_priority, "%s", msgbuf);
+ else {
+ ign = write (logfd, msgbuf, strlen (msgbuf));
+ if (ign) {} /* whatever; really shut up about unused result */
+
+ fsync (logfd); /* Make sure output is dumped to disk immediately */
+ }
+ }
+
+ g_free (msg);
+}
+
+static void
+log_handler (const gchar *log_domain,
+ GLogLevelFlags level,
+ const gchar *message,
+ gpointer ignored)
+{
+ int syslog_priority;
+ ssize_t ign;
+
+ switch (level) {
+ case G_LOG_LEVEL_ERROR:
+ syslog_priority = LOG_CRIT;
+ break;
+ case G_LOG_LEVEL_CRITICAL:
+ syslog_priority = LOG_ERR;
+ break;
+ case G_LOG_LEVEL_WARNING:
+ syslog_priority = LOG_WARNING;
+ break;
+ case G_LOG_LEVEL_MESSAGE:
+ syslog_priority = LOG_NOTICE;
+ break;
+ case G_LOG_LEVEL_DEBUG:
+ syslog_priority = LOG_DEBUG;
+ break;
+ case G_LOG_LEVEL_INFO:
+ default:
+ syslog_priority = LOG_INFO;
+ break;
+ }
+
+ if (logfd < 0)
+ syslog (syslog_priority, "%s", message);
+ else {
+ ign = write (logfd, message, strlen (message));
+ if (ign) {} /* whatever; really shut up about unused result */
+ }
+}
+
+gboolean
+mm_log_setup (const char *level,
+ const char *log_file,
+ gboolean show_timestamps,
+ gboolean rel_timestamps,
+ GError **error)
+{
+ /* levels */
+ if (level && strlen (level)) {
+ gboolean found = FALSE;
+ const LogDesc *diter;
+
+ for (diter = &level_descs[0]; diter->name; diter++) {
+ if (!strcasecmp (diter->name, level)) {
+ log_level = diter->num;
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found) {
+ g_set_error (error, 0, 0, "Unknown log level '%s'", level);
+ return FALSE;
+ }
+ }
+
+ if (show_timestamps)
+ ts_flags = TS_FLAG_WALL;
+ else if (rel_timestamps)
+ ts_flags = TS_FLAG_REL;
+
+ /* Grab start time for relative timestamps */
+ g_get_current_time (&rel_start);
+
+ if (log_file == NULL)
+ openlog (G_LOG_DOMAIN, LOG_CONS | LOG_PID | LOG_PERROR, LOG_DAEMON);
+ else {
+ logfd = open (log_file,
+ O_CREAT | O_APPEND | O_WRONLY,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+ if (logfd < 0) {
+ g_set_error (error, 0, 0, "Failed to open log file: (%d) %s",
+ errno, strerror (errno));
+ return FALSE;
+ }
+ }
+
+ g_log_set_handler (G_LOG_DOMAIN,
+ G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
+ log_handler,
+ NULL);
+
+ return TRUE;
+}
+
+void
+mm_log_usr1 (void)
+{
+}
+
+void
+mm_log_shutdown (void)
+{
+ if (logfd < 0)
+ closelog ();
+ else
+ close (logfd);
+}
+
diff --git a/src/mm-log.h b/src/mm-log.h
new file mode 100644
index 0000000..9b0d875
--- /dev/null
+++ b/src/mm-log.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ */
+
+#ifndef MM_LOG_H
+#define MM_LOG_H
+
+#include <glib.h>
+
+/* Log levels */
+enum {
+ LOGL_ERR = 0x00000001,
+ LOGL_WARN = 0x00000002,
+ LOGL_INFO = 0x00000004,
+ LOGL_DEBUG = 0x00000008
+};
+
+#define mm_err(...) \
+ _mm_log (G_STRLOC, G_STRFUNC, LOGL_ERR, ## __VA_ARGS__ )
+
+#define mm_warn(...) \
+ _mm_log (G_STRLOC, G_STRFUNC, LOGL_WARN, ## __VA_ARGS__ )
+
+#define mm_info(...) \
+ _mm_log (G_STRLOC, G_STRFUNC, LOGL_INFO, ## __VA_ARGS__ )
+
+#define mm_dbg(...) \
+ _mm_log (G_STRLOC, G_STRFUNC, LOGL_DEBUG, ## __VA_ARGS__ )
+
+#define mm_log(level, ...) \
+ _mm_log (G_STRLOC, G_STRFUNC, level, ## __VA_ARGS__ )
+
+void _mm_log (const char *loc,
+ const char *func,
+ guint32 level,
+ const char *fmt,
+ ...) __attribute__((__format__ (__printf__, 4, 5)));
+
+gboolean mm_log_setup (const char *level,
+ const char *log_file,
+ gboolean show_ts,
+ gboolean rel_ts,
+ GError **error);
+
+void mm_log_usr1 (void);
+
+void mm_log_shutdown (void);
+
+#endif /* MM_LOG_H */
+
diff --git a/src/mm-manager.c b/src/mm-manager.c
index 1dd1902..561d427 100644
--- a/src/mm-manager.c
+++ b/src/mm-manager.c
@@ -24,6 +24,7 @@
#include "mm-manager.h"
#include "mm-errors.h"
#include "mm-plugin.h"
+#include "mm-log.h"
static gboolean impl_manager_enumerate_devices (MMManager *manager,
GPtrArray **devices,
@@ -112,9 +113,9 @@ load_plugin (const char *path)
plugin = (*plugin_create_func) ();
if (plugin) {
g_object_weak_ref (G_OBJECT (plugin), (GWeakNotify) g_module_close, module);
- g_message ("Loaded plugin %s", mm_plugin_get_name (plugin));
+ mm_info ("Loaded plugin %s", mm_plugin_get_name (plugin));
} else
- g_warning ("Could not load plugin %s: initialization failed", path);
+ mm_warn ("Could not load plugin %s: initialization failed", path);
out:
if (!plugin)
@@ -196,7 +197,7 @@ remove_modem (MMManager *manager, MMModem *modem)
device = mm_modem_get_device (modem);
g_assert (device);
- g_debug ("Removed modem %s", device);
+ mm_dbg ("Removed modem %s", device);
g_signal_emit (manager, signals[DEVICE_REMOVED], 0, modem);
g_hash_table_remove (priv->modems, device);
@@ -234,8 +235,8 @@ check_export_modem (MMManager *self, MMModem *modem)
SupportsInfo *info = value;
if (!strcmp (info->physdev_path, modem_physdev)) {
- g_debug ("(%s/%s): outstanding support task prevents export of %s",
- info->subsys, info->name, modem_physdev);
+ mm_dbg ("(%s/%s): outstanding support task prevents export of %s",
+ info->subsys, info->name, modem_physdev);
goto out;
}
}
@@ -248,19 +249,35 @@ check_export_modem (MMManager *self, MMModem *modem)
/* No outstanding port tasks, so if the modem is valid we can export it */
if (mm_modem_get_valid (modem)) {
- static guint32 id = 0;
+ static guint32 id = 0, vid = 0, pid = 0;
char *path, *data_device = NULL;
+ GUdevDevice *physdev;
+ const char *subsys = NULL;
path = g_strdup_printf (MM_DBUS_PATH"/Modems/%d", id++);
dbus_g_connection_register_g_object (priv->connection, path, G_OBJECT (modem));
g_object_set_data_full (G_OBJECT (modem), DBUS_PATH_TAG, path, (GDestroyNotify) g_free);
- g_debug ("Exported modem %s as %s", modem_physdev, path);
-
- g_object_get (G_OBJECT (modem), MM_MODEM_DATA_DEVICE, &data_device, NULL);
- g_debug ("(%s): data port is %s", path, data_device);
+ mm_dbg ("Exported modem %s as %s", modem_physdev, path);
+
+ physdev = g_udev_client_query_by_sysfs_path (priv->udev, modem_physdev);
+ if (physdev)
+ subsys = g_udev_device_get_subsystem (physdev);
+
+ g_object_get (G_OBJECT (modem),
+ MM_MODEM_DATA_DEVICE, &data_device,
+ MM_MODEM_HW_VID, &vid,
+ MM_MODEM_HW_PID, &pid,
+ NULL);
+ mm_dbg ("(%s): VID 0x%04X PID 0x%04X (%s)",
+ path, (vid & 0xFFFF), (pid & 0xFFFF),
+ subsys ? subsys : "unknown");
+ mm_dbg ("(%s): data port is %s", path, data_device);
g_free (data_device);
+ if (physdev)
+ g_object_unref (physdev);
+
g_signal_emit (self, signals[DEVICE_ADDED], 0, modem);
}
@@ -293,7 +310,7 @@ add_modem (MMManager *manager, MMModem *modem, MMPlugin *plugin)
g_hash_table_insert (priv->modems, g_strdup (device), modem);
g_object_set_data (G_OBJECT (modem), MANAGER_PLUGIN_TAG, plugin);
- g_debug ("Added modem %s", device);
+ mm_dbg ("Added modem %s", device);
g_signal_connect (modem, "notify::" MM_MODEM_VALID, G_CALLBACK (modem_valid), manager);
check_export_modem (manager, modem);
}
@@ -430,7 +447,7 @@ supports_defer_timeout (gpointer user_data)
existing = find_modem_for_device (info->manager, info->physdev_path);
- g_debug ("(%s): re-checking support...", info->name);
+ mm_dbg ("(%s): re-checking support...", info->name);
try_supports_port (info->manager,
MM_PLUGIN (info->cur_plugin->data),
existing,
@@ -462,9 +479,9 @@ try_supports_port (MMManager *manager,
supports_callback (plugin, info->subsys, info->name, 0, info);
break;
case MM_PLUGIN_SUPPORTS_PORT_DEFER:
- g_debug ("(%s): (%s) deferring support check",
- mm_plugin_get_name (plugin),
- info->name);
+ mm_dbg ("(%s): (%s) deferring support check",
+ mm_plugin_get_name (plugin),
+ info->name);
if (info->defer_id)
g_source_remove (info->defer_id);
@@ -533,22 +550,21 @@ do_grab_port (gpointer user_data)
type_name = "CDMA";
device = mm_modem_get_device (modem);
- g_message ("(%s): %s modem %s claimed port %s",
- mm_plugin_get_name (info->best_plugin),
- type_name,
- device,
- info->name);
+ mm_info ("(%s): %s modem %s claimed port %s",
+ mm_plugin_get_name (info->best_plugin),
+ type_name,
+ device,
+ info->name);
g_free (device);
add_modem (info->manager, modem, info->best_plugin);
} else {
- g_warning ("%s: plugin '%s' claimed to support %s/%s but couldn't: (%d) %s",
- __func__,
- mm_plugin_get_name (info->best_plugin),
- info->subsys,
- info->name,
- error ? error->code : -1,
- (error && error->message) ? error->message : "(unknown)");
+ mm_warn ("plugin '%s' claimed to support %s/%s but couldn't: (%d) %s",
+ mm_plugin_get_name (info->best_plugin),
+ info->subsys,
+ info->name,
+ error ? error->code : -1,
+ (error && error->message) ? error->message : "(unknown)");
modem = existing;
}
}
@@ -597,8 +613,8 @@ supports_callback (MMPlugin *plugin,
* support this port, but this plugin is clearly the right plugin
* since it claimed this port's physical modem, just drop the port.
*/
- g_debug ("(%s/%s): ignoring port unsupported by physical modem's plugin",
- info->subsys, info->name);
+ mm_dbg ("(%s/%s): ignoring port unsupported by physical modem's plugin",
+ info->subsys, info->name);
supports_cleanup (info->manager, info->subsys, info->name, existing);
return;
}
@@ -615,14 +631,14 @@ supports_callback (MMPlugin *plugin,
*/
next_plugin = existing_plugin;
} else {
- g_debug ("(%s/%s): plugin %p (%s) existing %p (%s) info->best %p (%s)",
- info->subsys, info->name,
- plugin,
- plugin ? mm_plugin_get_name (plugin) : "none",
- existing_plugin,
- existing_plugin ? mm_plugin_get_name (existing_plugin) : "none",
- info->best_plugin,
- info->best_plugin ? mm_plugin_get_name (info->best_plugin) : "none");
+ mm_dbg ("(%s/%s): plugin %p (%s) existing %p (%s) info->best %p (%s)",
+ info->subsys, info->name,
+ plugin,
+ plugin ? mm_plugin_get_name (plugin) : "none",
+ existing_plugin,
+ existing_plugin ? mm_plugin_get_name (existing_plugin) : "none",
+ info->best_plugin,
+ info->best_plugin ? mm_plugin_get_name (info->best_plugin) : "none");
g_assert_not_reached ();
}
} else {
@@ -717,7 +733,7 @@ device_added (MMManager *manager, GUdevDevice *device)
const char *subsys, *name, *physdev_path, *physdev_subsys;
SupportsInfo *info;
char *key;
- gboolean found;
+ gboolean found, is_candidate;
GUdevDevice *physdev = NULL;
MMPlugin *plugin;
MMModem *existing;
@@ -734,6 +750,17 @@ device_added (MMManager *manager, GUdevDevice *device)
if (strncmp (name, "tty", 3) == 0 && isdigit (name[3]))
return;
+ /* Ignore devices that aren't completely configured by udev yet. If
+ * ModemManager is started in parallel with udev, explicitly requesting
+ * devices may return devices for which not all udev rules have yet been
+ * applied (a bug in udev/gudev). Since we often need those rules to match
+ * the device to a specific ModemManager driver, we need to ensure that all
+ * rules have been processed before handling a device.
+ */
+ is_candidate = g_udev_device_get_property_as_boolean (device, "ID_MM_CANDIDATE");
+ if (!is_candidate)
+ return;
+
if (find_modem_for_port (manager, subsys, name))
return;
@@ -756,14 +783,14 @@ device_added (MMManager *manager, GUdevDevice *device)
&& strcmp (name, "lo")
&& strcmp (name, "tty")
&& !strstr (name, "virbr"))
- g_debug ("(%s/%s): could not get port's parent device", subsys, name);
+ mm_dbg ("(%s/%s): could not get port's parent device", subsys, name);
goto out;
}
/* Is the device blacklisted? */
if (g_udev_device_get_property_as_boolean (physdev, "ID_MM_DEVICE_IGNORE")) {
- g_debug ("(%s/%s): port's parent device is blacklisted", subsys, name);
+ mm_dbg ("(%s/%s): port's parent device is blacklisted", subsys, name);
goto out;
}
@@ -772,13 +799,13 @@ device_added (MMManager *manager, GUdevDevice *device)
if ( physdev_subsys
&& !strcmp (physdev_subsys, "platform")
&& !g_udev_device_get_property_as_boolean (physdev, "ID_MM_PLATFORM_DRIVER_PROBE")) {
- g_debug ("(%s/%s): port's parent platform driver is not whitelisted", subsys, name);
+ mm_dbg ("(%s/%s): port's parent platform driver is not whitelisted", subsys, name);
goto out;
}
physdev_path = g_udev_device_get_sysfs_path (physdev);
if (!physdev_path) {
- g_debug ("(%s/%s): could not get port's parent device sysfs path", subsys, name);
+ mm_dbg ("(%s/%s): could not get port's parent device sysfs path", subsys, name);
goto out;
}
@@ -809,7 +836,7 @@ device_removed (MMManager *manager, GUdevDevice *device)
MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
MMModem *modem;
const char *subsys, *name;
- char *key;
+ char *key, *modem_device;
SupportsInfo *info;
g_return_if_fail (device != NULL);
@@ -824,6 +851,9 @@ device_removed (MMManager *manager, GUdevDevice *device)
/* find_modem_for_port handles tty and net removal */
modem = find_modem_for_port (manager, subsys, name);
if (modem) {
+ modem_device = mm_modem_get_device (modem);
+ mm_info ("(%s/%s): released by modem %s", subsys, name, modem_device);
+ g_free (modem_device);
mm_modem_release_port (modem, subsys, name);
return;
}
@@ -838,10 +868,9 @@ device_removed (MMManager *manager, GUdevDevice *device)
*/
const char *sysfs_path = g_udev_device_get_sysfs_path (device);
- // g_debug ("Looking for a modem for removed device %s", sysfs_path);
modem = find_modem_for_device (manager, sysfs_path);
if (modem) {
- g_debug ("Removing modem claimed by removed device %s", sysfs_path);
+ mm_dbg ("Removing modem claimed by removed device %s", sysfs_path);
remove_modem (manager, modem);
return;
}
diff --git a/src/mm-modem-base.c b/src/mm-modem-base.c
index 0c39d2c..ee5e1c8 100644
--- a/src/mm-modem-base.c
+++ b/src/mm-modem-base.c
@@ -14,6 +14,7 @@
* Copyright (C) 2009 Red Hat, Inc.
*/
+#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@@ -24,17 +25,19 @@
#include "mm-at-serial-port.h"
#include "mm-qcdm-serial-port.h"
#include "mm-errors.h"
-#include "mm-options.h"
+#include "mm-log.h"
#include "mm-properties-changed-signal.h"
#include "mm-callback-info.h"
#include "mm-modem-helpers.h"
static void modem_init (MMModem *modem_class);
+static void pc_init (MMPropertiesChanged *pc_class);
G_DEFINE_TYPE_EXTENDED (MMModemBase, mm_modem_base,
G_TYPE_OBJECT,
G_TYPE_FLAG_VALUE_ABSTRACT,
- G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init))
+ G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_PROPERTIES_CHANGED, pc_init))
#define MM_MODEM_BASE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_BASE, MMModemBasePrivate))
@@ -43,15 +46,21 @@ typedef struct {
char *plugin;
char *device;
char *equipment_ident;
+ char *device_ident;
char *unlock_required;
guint32 unlock_retries;
guint32 ip_method;
gboolean valid;
MMModemState state;
+ guint vid;
+ guint pid;
char *manf;
char *model;
char *revision;
+ char *ati;
+ char *ati1;
+ char *gsn;
MMAuthProvider *authp;
@@ -139,15 +148,13 @@ mm_modem_base_add_port (MMModemBase *self,
if (!port)
return NULL;
- if (mm_options_debug ()) {
- device = mm_modem_get_device (MM_MODEM (self));
+ device = mm_modem_get_device (MM_MODEM (self));
+ mm_dbg ("(%s) type %s claimed by %s",
+ name,
+ mm_port_type_to_name (ptype),
+ device);
+ g_free (device);
- g_message ("(%s) type %s claimed by %s",
- name,
- mm_port_type_to_name (ptype),
- device);
- g_free (device);
- }
key = get_hash_key (subsys, name);
g_hash_table_insert (priv->ports, key, port);
return port;
@@ -156,10 +163,32 @@ mm_modem_base_add_port (MMModemBase *self,
gboolean
mm_modem_base_remove_port (MMModemBase *self, MMPort *port)
{
+ MMModemBasePrivate *priv;
+ char *device, *key, *name;
+ const char *type_name, *subsys;
+ gboolean removed;
+
g_return_val_if_fail (MM_IS_MODEM_BASE (self), FALSE);
g_return_val_if_fail (port != NULL, FALSE);
- return g_hash_table_remove (MM_MODEM_BASE_GET_PRIVATE (self)->ports, port);
+ priv = MM_MODEM_BASE_GET_PRIVATE (self);
+
+ name = g_strdup (mm_port_get_device (port));
+ subsys = mm_port_subsys_to_name (mm_port_get_subsys (port));
+ type_name = mm_port_type_to_name (mm_port_get_port_type (port));
+
+ key = get_hash_key (subsys, name);
+ removed = g_hash_table_remove (priv->ports, key);
+ if (removed) {
+ /* Port may have already been destroyed by removal from the hash */
+ device = mm_modem_get_device (MM_MODEM (self));
+ mm_dbg ("(%s) type %s removed from %s", name, type_name, device);
+ g_free (device);
+ }
+ g_free (key);
+ g_free (name);
+
+ return removed;
}
void
@@ -224,9 +253,9 @@ mm_modem_base_set_equipment_identifier (MMModemBase *self, const char *ident)
dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG);
if (dbus_path) {
if (priv->equipment_ident)
- g_message ("Modem %s: Equipment identifier set (%s)", dbus_path, priv->equipment_ident);
+ mm_info ("Modem %s: Equipment identifier set (%s)", dbus_path, priv->equipment_ident);
else
- g_message ("Modem %s: Equipment identifier not set", dbus_path);
+ mm_warn ("Modem %s: Equipment identifier not set", dbus_path);
}
g_object_notify (G_OBJECT (self), MM_MODEM_EQUIPMENT_IDENTIFIER);
@@ -265,9 +294,9 @@ mm_modem_base_set_unlock_required (MMModemBase *self, const char *unlock_require
dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG);
if (dbus_path) {
if (priv->unlock_required)
- g_message ("Modem %s: unlock required (%s)", dbus_path, priv->unlock_required);
+ mm_info ("Modem %s: unlock required (%s)", dbus_path, priv->unlock_required);
else
- g_message ("Modem %s: unlock no longer required", dbus_path);
+ mm_info ("Modem %s: unlock no longer required", dbus_path);
}
g_object_notify (G_OBJECT (self), MM_MODEM_UNLOCK_REQUIRED);
@@ -301,8 +330,13 @@ mm_modem_base_set_unlock_retries (MMModemBase *self, guint unlock_retries)
dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG);
if (dbus_path) {
- g_message ("Modem %s: # unlock retries for %s is %d",
- dbus_path, priv->unlock_required, priv->unlock_retries);
+ if (priv->unlock_required) {
+ mm_info ("Modem %s: # unlock retries for %s is %d",
+ dbus_path, priv->unlock_required, priv->unlock_retries);
+ } else {
+ mm_info ("Modem %s: # unlock retries is %d",
+ dbus_path, priv->unlock_retries);
+ }
}
g_object_notify (G_OBJECT (self), MM_MODEM_UNLOCK_RETRIES);
@@ -391,7 +425,7 @@ card_info_cache_invoke (MMCallbackInfo *info)
MMModemBase *self = MM_MODEM_BASE (info->modem);
MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
MMModemInfoFn callback = (MMModemInfoFn) info->callback;
- const char *manf, *cmanf, *model, *cmodel, *rev, *crev;
+ const char *manf, *cmanf, *model, *cmodel, *rev, *crev, *ati, *ati1, *gsn, *cgsn;
manf = mm_callback_info_get_data (info, "card-info-manf");
cmanf = mm_callback_info_get_data (info, "card-info-c-manf");
@@ -410,6 +444,31 @@ card_info_cache_invoke (MMCallbackInfo *info)
g_free (priv->revision);
priv->revision = g_strdup (crev ? crev : rev);
+ ati = mm_callback_info_get_data (info, "card-info-ati");
+ g_free (priv->ati);
+ priv->ati = g_strdup (ati);
+
+ ati1 = mm_callback_info_get_data (info, "card-info-ati1");
+ g_free (priv->ati1);
+ priv->ati1 = g_strdup (ati1);
+
+ gsn = mm_callback_info_get_data (info, "card-info-gsn");
+ cgsn = mm_callback_info_get_data (info, "card-info-c-gsn");
+ g_free (priv->gsn);
+ priv->gsn = g_strdup (cgsn ? cgsn : gsn);
+
+ /* Build up the device identifier */
+ g_free (priv->device_ident);
+ priv->device_ident = mm_create_device_identifier (priv->vid,
+ priv->pid,
+ priv->ati,
+ priv->ati1,
+ priv->gsn,
+ priv->revision,
+ priv->model,
+ priv->manf);
+ g_object_notify (G_OBJECT (self), MM_MODEM_DEVICE_IDENTIFIER);
+
callback (info->modem, priv->manf, priv->model, priv->revision, info->error, info->user_data);
}
@@ -418,35 +477,44 @@ info_item_done (MMCallbackInfo *info,
GString *response,
GError *error,
const char *tag,
+ const char *tag2,
const char *desc)
{
- const char *p;
+ const char *p = response->str;
if (!error) {
- p = mm_strip_tag (response->str, tag);
+ if (tag)
+ p = mm_strip_tag (p, tag);
+ if (tag2)
+ p = mm_strip_tag (p, tag2);
mm_callback_info_set_data (info, desc, strlen (p) ? g_strdup (p) : NULL, g_free);
}
mm_callback_info_chain_complete_one (info);
}
-#define GET_INFO_RESP_FN(func_name, tag, desc) \
+#define GET_INFO_RESP_FN(func_name, tag, tag2, desc) \
static void \
func_name (MMAtSerialPort *port, \
GString *response, \
GError *error, \
gpointer user_data) \
{ \
- info_item_done ((MMCallbackInfo *) user_data, response, error, tag , desc ); \
+ info_item_done ((MMCallbackInfo *) user_data, response, error, tag, tag2, desc ); \
}
-GET_INFO_RESP_FN(get_revision_done, "+GMR:", "card-info-revision")
-GET_INFO_RESP_FN(get_model_done, "+GMM:", "card-info-model")
-GET_INFO_RESP_FN(get_manf_done, "+GMI:", "card-info-manf")
+GET_INFO_RESP_FN(get_revision_done, "+GMR:", "AT+GMR", "card-info-revision")
+GET_INFO_RESP_FN(get_model_done, "+GMM:", "AT+GMM", "card-info-model")
+GET_INFO_RESP_FN(get_manf_done, "+GMI:", "AT+GMI", "card-info-manf")
+
+GET_INFO_RESP_FN(get_c_revision_done, "+CGMR:", "AT+CGMR", "card-info-c-revision")
+GET_INFO_RESP_FN(get_c_model_done, "+CGMM:", "AT+CGMM", "card-info-c-model")
+GET_INFO_RESP_FN(get_c_manf_done, "+CGMI:", "AT+CGMI", "card-info-c-manf")
-GET_INFO_RESP_FN(get_c_revision_done, "+CGMR:", "card-info-c-revision")
-GET_INFO_RESP_FN(get_c_model_done, "+CGMM:", "card-info-c-model")
-GET_INFO_RESP_FN(get_c_manf_done, "+CGMI:", "card-info-c-manf")
+GET_INFO_RESP_FN(get_ati_done, NULL, "ATI", "card-info-ati")
+GET_INFO_RESP_FN(get_ati1_done, NULL, "ATI1", "card-info-ati1")
+GET_INFO_RESP_FN(get_gsn_done, "+GSN:", "AT+GSN", "card-info-gsn")
+GET_INFO_RESP_FN(get_cgsn_done, "+CGSN:", "AT+CGSN", "card-info-c-gsn")
void
mm_modem_base_get_card_info (MMModemBase *self,
@@ -457,7 +525,6 @@ mm_modem_base_get_card_info (MMModemBase *self,
{
MMModemBasePrivate *priv;
MMCallbackInfo *info;
- MMModemState state;
gboolean cached = FALSE;
GError *error = port_error;
@@ -474,16 +541,8 @@ mm_modem_base_get_card_info (MMModemBase *self,
*/
if (priv->manf || priv->model || priv->revision)
cached = TRUE;
- else {
- state = mm_modem_get_state (MM_MODEM (self));
-
- if (port_error)
- error = g_error_copy (port_error);
- else if (state < MM_MODEM_STATE_ENABLING) {
- error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
- "The modem is not enabled.");
- }
- }
+ else if (port_error)
+ error = g_error_copy (port_error);
/* If we have cached info or an error, don't hit up the card */
if (cached || error) {
@@ -502,13 +561,19 @@ mm_modem_base_get_card_info (MMModemBase *self,
G_CALLBACK (callback),
user_data);
- mm_callback_info_chain_start (info, 6);
+ mm_callback_info_chain_start (info, 10);
+
mm_at_serial_port_queue_command_cached (port, "+GMI", 3, get_manf_done, info);
mm_at_serial_port_queue_command_cached (port, "+GMM", 3, get_model_done, info);
mm_at_serial_port_queue_command_cached (port, "+GMR", 3, get_revision_done, info);
mm_at_serial_port_queue_command_cached (port, "+CGMI", 3, get_c_manf_done, info);
mm_at_serial_port_queue_command_cached (port, "+CGMM", 3, get_c_model_done, info);
mm_at_serial_port_queue_command_cached (port, "+CGMR", 3, get_c_revision_done, info);
+
+ mm_at_serial_port_queue_command_cached (port, "I", 3, get_ati_done, info);
+ mm_at_serial_port_queue_command_cached (port, "I1", 3, get_ati1_done, info);
+ mm_at_serial_port_queue_command_cached (port, "+GSN", 3, get_gsn_done, info);
+ mm_at_serial_port_queue_command_cached (port, "+CGSN", 3, get_cgsn_done, info);
}
/*****************************************************************************/
@@ -562,15 +627,27 @@ mm_modem_base_init (MMModemBase *self)
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_ENABLED,
+ NULL,
MM_MODEM_DBUS_INTERFACE);
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_EQUIPMENT_IDENTIFIER,
+ NULL,
+ MM_MODEM_DBUS_INTERFACE);
+ mm_properties_changed_signal_register_property (G_OBJECT (self),
+ MM_MODEM_DEVICE_IDENTIFIER,
+ NULL,
MM_MODEM_DBUS_INTERFACE);
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_UNLOCK_REQUIRED,
+ NULL,
MM_MODEM_DBUS_INTERFACE);
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_UNLOCK_RETRIES,
+ NULL,
+ MM_MODEM_DBUS_INTERFACE);
+ mm_properties_changed_signal_register_property (G_OBJECT (self),
+ MM_MODEM_IP_METHOD,
+ NULL,
MM_MODEM_DBUS_INTERFACE);
}
@@ -581,6 +658,11 @@ modem_init (MMModem *modem_class)
modem_class->auth_finish = modem_auth_finish;
}
+static void
+pc_init (MMPropertiesChanged *pc_class)
+{
+}
+
static gboolean
is_enabled (MMModemState state)
{
@@ -621,9 +703,18 @@ set_property (GObject *object, guint prop_id,
case MM_MODEM_PROP_TYPE:
case MM_MODEM_PROP_ENABLED:
case MM_MODEM_PROP_EQUIPMENT_IDENTIFIER:
+ case MM_MODEM_PROP_DEVICE_IDENTIFIER:
case MM_MODEM_PROP_UNLOCK_REQUIRED:
case MM_MODEM_PROP_UNLOCK_RETRIES:
break;
+ case MM_MODEM_PROP_HW_VID:
+ /* Construct only */
+ priv->vid = g_value_get_uint (value);
+ break;
+ case MM_MODEM_PROP_HW_PID:
+ /* Construct only */
+ priv->pid = g_value_get_uint (value);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -667,12 +758,21 @@ get_property (GObject *object, guint prop_id,
case MM_MODEM_PROP_EQUIPMENT_IDENTIFIER:
g_value_set_string (value, priv->equipment_ident);
break;
+ case MM_MODEM_PROP_DEVICE_IDENTIFIER:
+ g_value_set_string (value, priv->device_ident);
+ break;
case MM_MODEM_PROP_UNLOCK_REQUIRED:
g_value_set_string (value, priv->unlock_required);
break;
case MM_MODEM_PROP_UNLOCK_RETRIES:
g_value_set_uint (value, priv->unlock_retries);
break;
+ case MM_MODEM_PROP_HW_VID:
+ g_value_set_uint (value, priv->vid);
+ break;
+ case MM_MODEM_PROP_HW_PID:
+ g_value_set_uint (value, priv->pid);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -692,7 +792,14 @@ finalize (GObject *object)
g_free (priv->plugin);
g_free (priv->device);
g_free (priv->equipment_ident);
+ g_free (priv->device_ident);
g_free (priv->unlock_required);
+ g_free (priv->manf);
+ g_free (priv->model);
+ g_free (priv->revision);
+ g_free (priv->ati);
+ g_free (priv->ati1);
+ g_free (priv->gsn);
G_OBJECT_CLASS (mm_modem_base_parent_class)->finalize (object);
}
@@ -750,6 +857,10 @@ mm_modem_base_class_init (MMModemBaseClass *klass)
MM_MODEM_EQUIPMENT_IDENTIFIER);
g_object_class_override_property (object_class,
+ MM_MODEM_PROP_DEVICE_IDENTIFIER,
+ MM_MODEM_DEVICE_IDENTIFIER);
+
+ g_object_class_override_property (object_class,
MM_MODEM_PROP_UNLOCK_REQUIRED,
MM_MODEM_UNLOCK_REQUIRED);
@@ -757,6 +868,14 @@ mm_modem_base_class_init (MMModemBaseClass *klass)
MM_MODEM_PROP_UNLOCK_RETRIES,
MM_MODEM_UNLOCK_RETRIES);
- mm_properties_changed_signal_new (object_class);
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_HW_VID,
+ MM_MODEM_HW_VID);
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_HW_PID,
+ MM_MODEM_HW_PID);
+
+ mm_properties_changed_signal_enable (object_class);
}
diff --git a/src/mm-modem-cdma.c b/src/mm-modem-cdma.c
index e80dc4e..722918e 100644
--- a/src/mm-modem-cdma.c
+++ b/src/mm-modem-cdma.c
@@ -26,6 +26,8 @@ static void impl_modem_cdma_get_signal_quality (MMModemCdma *modem, DBusGMethodI
static void impl_modem_cdma_get_esn (MMModemCdma *modem, DBusGMethodInvocation *context);
static void impl_modem_cdma_get_serving_system (MMModemCdma *modem, DBusGMethodInvocation *context);
static void impl_modem_cdma_get_registration_state (MMModemCdma *modem, DBusGMethodInvocation *context);
+static void impl_modem_cdma_activate (MMModemCdma *modem, DBusGMethodInvocation *context);
+static void impl_modem_cdma_activate_manual (MMModemCdma *modem, DBusGMethodInvocation *context);
#include "mm-modem-cdma-glue.h"
@@ -251,6 +253,39 @@ mm_modem_cdma_emit_signal_quality_changed (MMModemCdma *self, guint32 quality)
g_signal_emit (self, signals[SIGNAL_QUALITY], 0, quality);
}
+void mm_modem_cdma_activate(MMModemCdma *self, MMModemUIntFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_CDMA (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_CDMA_GET_INTERFACE (self)->activate)
+ MM_MODEM_CDMA_GET_INTERFACE (self)->activate(self, callback, user_data);
+ else
+ uint_op_not_supported (MM_MODEM (self), callback, user_data);
+}
+
+static void impl_modem_cdma_activate(MMModemCdma *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_cdma_activate (modem, uint_call_done, context);
+}
+
+
+void mm_modem_cdma_activate_manual(MMModemCdma *self, MMModemUIntFn callback,
+ gpointer user_data) {
+ g_return_if_fail (MM_IS_MODEM_CDMA (self));
+ g_return_if_fail (callback != NULL);
+ if (MM_MODEM_CDMA_GET_INTERFACE (self)->activate_manual)
+ MM_MODEM_CDMA_GET_INTERFACE (self)->activate_manual(self, callback, user_data);
+ else
+ uint_op_not_supported (MM_MODEM (self), callback, user_data);
+}
+static void impl_modem_cdma_activate_manual (MMModemCdma *modem,
+ DBusGMethodInvocation *context) {
+ mm_modem_cdma_activate_manual(modem, uint_call_done, context);
+}
+
/*****************************************************************************/
static void
@@ -322,6 +357,15 @@ mm_modem_cdma_init (gpointer g_iface)
if (initialized)
return;
+ /* Properties */
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_string (MM_MODEM_CDMA_MEID,
+ "MEID",
+ "MEID",
+ NULL,
+ G_PARAM_READABLE));
+
/* Signals */
signals[SIGNAL_QUALITY] =
g_signal_new ("signal-quality",
diff --git a/src/mm-modem-cdma.h b/src/mm-modem-cdma.h
index a7a626f..4d30386 100644
--- a/src/mm-modem-cdma.h
+++ b/src/mm-modem-cdma.h
@@ -35,6 +35,14 @@ typedef enum {
#define MM_MODEM_CDMA_REGISTRATION_STATE_CHANGED "registration-state-changed"
+#define MM_MODEM_CDMA_MEID "meid"
+
+typedef enum {
+ MM_MODEM_CDMA_PROP_FIRST = 0x1200,
+
+ MM_MODEM_CDMA_PROP_MEID = MM_MODEM_CDMA_PROP_FIRST,
+} MMModemCdmaProp;
+
typedef struct _MMModemCdma MMModemCdma;
typedef void (*MMModemCdmaServingSystemFn) (MMModemCdma *modem,
@@ -70,6 +78,18 @@ struct _MMModemCdma {
MMModemCdmaRegistrationStateFn callback,
gpointer user_data);
+ void (*activate) (MMModemCdma *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+ void (*activate_manual) (MMModemCdma *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+ void (*activate_manual_debug) (MMModemCdma *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
/* Signals */
void (*signal_quality) (MMModemCdma *self,
guint32 quality);
@@ -97,6 +117,12 @@ void mm_modem_cdma_get_registration_state (MMModemCdma *self,
MMModemCdmaRegistrationStateFn callback,
gpointer user_data);
+void mm_modem_cdma_activate (MMModemCdma *self, MMModemUIntFn callback,
+ gpointer user_data);
+
+void mm_modem_cdma_activate_manual (MMModemCdma *self, MMModemUIntFn callback,
+ gpointer user_data);
+
/* Protected */
void mm_modem_cdma_emit_signal_quality_changed (MMModemCdma *self, guint32 new_quality);
diff --git a/src/mm-modem-gsm-card.c b/src/mm-modem-gsm-card.c
index e03b964..d04f014 100644
--- a/src/mm-modem-gsm-card.c
+++ b/src/mm-modem-gsm-card.c
@@ -581,6 +581,14 @@ mm_modem_gsm_card_init (gpointer g_iface)
g_object_interface_install_property
(g_iface,
+ g_param_spec_string (MM_MODEM_GSM_CARD_SIM_IDENTIFIER,
+ "SimIdentifier",
+ "An obfuscated identifier of the SIM",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_interface_install_property
+ (g_iface,
g_param_spec_uint (MM_MODEM_GSM_CARD_SUPPORTED_BANDS,
"Supported Modes",
"Supported frequency bands of the card",
diff --git a/src/mm-modem-gsm-card.h b/src/mm-modem-gsm-card.h
index 584d734..e617d8f 100644
--- a/src/mm-modem-gsm-card.h
+++ b/src/mm-modem-gsm-card.h
@@ -26,6 +26,7 @@
#define MM_MODEM_GSM_CARD_SUPPORTED_BANDS "supported-bands"
#define MM_MODEM_GSM_CARD_SUPPORTED_MODES "supported-modes"
+#define MM_MODEM_GSM_CARD_SIM_IDENTIFIER "sim-identifier"
#define MM_MODEM_GSM_CARD_SIM_PIN "sim-pin"
#define MM_MODEM_GSM_CARD_SIM_PIN2 "sim-pin2"
diff --git a/src/mm-modem-gsm-network.c b/src/mm-modem-gsm-network.c
index 4cd6921..75ca7de 100644
--- a/src/mm-modem-gsm-network.c
+++ b/src/mm-modem-gsm-network.c
@@ -108,6 +108,8 @@ mm_modem_gsm_network_act_to_old_mode (MMModemGsmAccessTech act)
return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSUPA;
else if (act & MM_MODEM_GSM_ACCESS_TECH_HSPA)
return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSPA;
+ else if (act & MM_MODEM_GSM_ACCESS_TECH_HSPA_PLUS)
+ return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSPA;
return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY;
}
diff --git a/src/mm-modem-gsm-ussd.c b/src/mm-modem-gsm-ussd.c
new file mode 100644
index 0000000..f90a845
--- /dev/null
+++ b/src/mm-modem-gsm-ussd.c
@@ -0,0 +1,378 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2010 Guido Guenther <agx@sigxcpu.org>
+ */
+
+#include <string.h>
+#include <dbus/dbus-glib.h>
+
+#include "mm-modem-gsm-ussd.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+#include "mm-marshal.h"
+
+static void impl_modem_gsm_ussd_initiate(MMModemGsmUssd *modem,
+ const char*command,
+ DBusGMethodInvocation *context);
+
+static void impl_modem_gsm_ussd_respond(MMModemGsmUssd *modem,
+ const char *response,
+ DBusGMethodInvocation *context);
+
+static void impl_modem_gsm_ussd_cancel(MMModemGsmUssd *modem,
+ DBusGMethodInvocation *context);
+
+#include "mm-modem-gsm-ussd-glue.h"
+
+
+/*****************************************************************************/
+
+static void
+str_call_done (MMModem *modem, const char *result, GError *error, gpointer user_data)
+{
+ DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+
+ if (error)
+ dbus_g_method_return_error (context, error);
+ else
+ dbus_g_method_return (context, result);
+}
+
+static void
+str_call_not_supported (MMModemGsmUssd *self,
+ MMModemStringFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_string_new (MM_MODEM (self), callback, user_data);
+ info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
+ "Operation not supported");
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+async_call_done (MMModem *modem, GError *error, gpointer user_data)
+{
+ DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+
+ if (error)
+ dbus_g_method_return_error (context, error);
+ else
+ dbus_g_method_return (context);
+}
+
+static void
+async_call_not_supported (MMModemGsmUssd *self,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new (MM_MODEM (self), callback, user_data);
+ info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
+ "Operation not supported");
+ mm_callback_info_schedule (info);
+}
+
+/*****************************************************************************/
+
+void
+mm_modem_gsm_ussd_initiate (MMModemGsmUssd *self,
+ const char *command,
+ MMModemStringFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_USSD (self));
+ g_return_if_fail (command != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_USSD_GET_INTERFACE (self)->initiate)
+ MM_MODEM_GSM_USSD_GET_INTERFACE (self)->initiate(self, command, callback, user_data);
+ else
+ str_call_not_supported (self, callback, user_data);
+
+}
+
+void
+mm_modem_gsm_ussd_respond (MMModemGsmUssd *self,
+ const char *command,
+ MMModemStringFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_USSD (self));
+ g_return_if_fail (command != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_USSD_GET_INTERFACE (self)->respond)
+ MM_MODEM_GSM_USSD_GET_INTERFACE (self)->respond(self, command, callback, user_data);
+ else
+ str_call_not_supported (self, callback, user_data);
+
+}
+
+void
+mm_modem_gsm_ussd_cancel (MMModemGsmUssd *self,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_USSD (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_USSD_GET_INTERFACE (self)->cancel)
+ MM_MODEM_GSM_USSD_GET_INTERFACE (self)->cancel(self, callback, user_data);
+ else
+ async_call_not_supported (self, callback, user_data);
+
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ char *command;
+} UssdAuthInfo;
+
+static void
+ussd_auth_info_destroy (gpointer data)
+{
+ UssdAuthInfo *info = data;
+
+ g_free (info->command);
+ g_free (info);
+}
+
+static UssdAuthInfo *
+ussd_auth_info_new (const char* command)
+{
+ UssdAuthInfo *info;
+
+ info = g_malloc0 (sizeof (UssdAuthInfo));
+ info->command = g_strdup (command);
+
+ return info;
+}
+
+/*****************************************************************************/
+
+static void
+ussd_initiate_auth_cb (MMAuthRequest *req,
+ GObject *owner,
+ DBusGMethodInvocation *context,
+ gpointer user_data)
+{
+ MMModemGsmUssd *self = MM_MODEM_GSM_USSD (owner);
+ UssdAuthInfo *info = user_data;
+ GError *error = NULL;
+
+ /* Return any authorization error, otherwise initiate the USSD */
+ if (!mm_modem_auth_finish (MM_MODEM (self), req, &error))
+ goto done;
+
+ if (!info->command) {
+ error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Missing USSD command");
+ }
+
+done:
+ if (error) {
+ str_call_done (MM_MODEM (self), NULL, error, context);
+ g_error_free (error);
+ } else
+ mm_modem_gsm_ussd_initiate (self, info->command, str_call_done, context);
+}
+
+static void
+impl_modem_gsm_ussd_initiate (MMModemGsmUssd *modem,
+ const char *command,
+ DBusGMethodInvocation *context)
+{
+ GError *error = NULL;
+ UssdAuthInfo *info;
+
+ info = ussd_auth_info_new (command);
+
+ /* Make sure the caller is authorized to initiate the USSD */
+ if (!mm_modem_auth_request (MM_MODEM (modem),
+ MM_AUTHORIZATION_USSD,
+ context,
+ ussd_initiate_auth_cb,
+ info,
+ ussd_auth_info_destroy,
+ &error)) {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+}
+
+static void
+ussd_respond_auth_cb (MMAuthRequest *req,
+ GObject *owner,
+ DBusGMethodInvocation *context,
+ gpointer user_data)
+{
+ MMModemGsmUssd *self = MM_MODEM_GSM_USSD (owner);
+ UssdAuthInfo *info = user_data;
+ GError *error = NULL;
+
+ /* Return any authorization error, otherwise respond to the USSD */
+ if (!mm_modem_auth_finish (MM_MODEM (self), req, &error))
+ goto done;
+
+ if (!info->command) {
+ error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Missing USSD command");
+ }
+
+done:
+ if (error) {
+ str_call_done (MM_MODEM (self), NULL, error, context);
+ g_error_free (error);
+ } else
+ mm_modem_gsm_ussd_respond (self, info->command, str_call_done, context);
+}
+
+static void
+impl_modem_gsm_ussd_respond (MMModemGsmUssd *modem,
+ const char *command,
+ DBusGMethodInvocation *context)
+{
+ GError *error = NULL;
+ UssdAuthInfo *info;
+
+ info = ussd_auth_info_new (command);
+
+ /* Make sure the caller is authorized to respond to the USSD */
+ if (!mm_modem_auth_request (MM_MODEM (modem),
+ MM_AUTHORIZATION_USSD,
+ context,
+ ussd_respond_auth_cb,
+ info,
+ ussd_auth_info_destroy,
+ &error)) {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+}
+
+static void
+ussd_cancel_auth_cb (MMAuthRequest *req,
+ GObject *owner,
+ DBusGMethodInvocation *context,
+ gpointer user_data)
+{
+ MMModemGsmUssd *self = MM_MODEM_GSM_USSD (owner);
+ GError *error = NULL;
+
+ /* Return any authorization error, otherwise cancel the USSD */
+ mm_modem_auth_finish (MM_MODEM (self), req, &error);
+
+ if (error) {
+ str_call_done (MM_MODEM (self), NULL, error, context);
+ g_error_free (error);
+ } else
+ mm_modem_gsm_ussd_cancel (self, async_call_done, context);
+}
+
+static void
+impl_modem_gsm_ussd_cancel (MMModemGsmUssd *modem,
+ DBusGMethodInvocation *context)
+{
+ GError *error = NULL;
+ UssdAuthInfo *info;
+
+ info = ussd_auth_info_new (NULL);
+
+ /* Make sure the caller is authorized to cancel USSD */
+ if (!mm_modem_auth_request (MM_MODEM (modem),
+ MM_AUTHORIZATION_USSD,
+ context,
+ ussd_cancel_auth_cb,
+ info,
+ ussd_auth_info_destroy,
+ &error)) {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+}
+
+/*****************************************************************************/
+
+static void
+mm_modem_gsm_ussd_init (gpointer g_iface)
+{
+ static gboolean initialized = FALSE;
+
+ if (initialized)
+ return;
+
+ /* Properties */
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_string (MM_MODEM_GSM_USSD_STATE,
+ "State",
+ "Current state of USSD session",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_string (MM_MODEM_GSM_USSD_NETWORK_NOTIFICATION,
+ "NetworkNotification",
+ "Network initiated request, no response required",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_string (MM_MODEM_GSM_USSD_NETWORK_REQUEST,
+ "NetworkRequest",
+ "Network initiated request, reponse required",
+ NULL,
+ G_PARAM_READABLE));
+
+ initialized = TRUE;
+}
+
+GType
+mm_modem_gsm_ussd_get_type (void)
+{
+ static GType ussd_type = 0;
+
+ if (!G_UNLIKELY (ussd_type)) {
+ const GTypeInfo ussd_info = {
+ sizeof (MMModemGsmUssd), /* class_size */
+ mm_modem_gsm_ussd_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL
+ };
+
+ ussd_type = g_type_register_static (G_TYPE_INTERFACE,
+ "MMModemGsmUssd",
+ &ussd_info, 0);
+
+ g_type_interface_add_prerequisite (ussd_type, G_TYPE_OBJECT);
+ dbus_g_object_type_install_info (ussd_type, &dbus_glib_mm_modem_gsm_ussd_object_info);
+
+ dbus_g_object_type_register_shadow_property (ussd_type,
+ "State",
+ MM_MODEM_GSM_USSD_STATE);
+ }
+
+ return ussd_type;
+}
diff --git a/src/mm-modem-gsm-ussd.h b/src/mm-modem-gsm-ussd.h
new file mode 100644
index 0000000..c8f652b
--- /dev/null
+++ b/src/mm-modem-gsm-ussd.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2010 Guido Guenther <agx@sigxcpu.org>
+ */
+
+#ifndef MM_MODEM_GSM_USSD_H
+#define MM_MODEM_GSM_USSD_H
+
+#include <mm-modem.h>
+
+typedef enum {
+ MM_MODEM_GSM_USSD_STATE_IDLE = 0x00000000,
+ MM_MODEM_GSM_USSD_STATE_ACTIVE = 0x00000001,
+ MM_MODEM_GSM_USSD_STATE_USER_RESPONSE = 0x00000002,
+} MMModemGsmUssdState;
+
+#define MM_MODEM_GSM_USSD_STATE "ussd-state"
+#define MM_MODEM_GSM_USSD_NETWORK_NOTIFICATION "network-notification"
+#define MM_MODEM_GSM_USSD_NETWORK_REQUEST "network-request"
+
+#define MM_TYPE_MODEM_GSM_USSD (mm_modem_gsm_ussd_get_type ())
+#define MM_MODEM_GSM_USSD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_GSM_USSD, MMModemGsmUssd))
+#define MM_IS_MODEM_GSM_USSD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_GSM_USSD))
+#define MM_MODEM_GSM_USSD_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_GSM_USSD, MMModemGsmUssd))
+
+#define MM_MODEM_GSM_USSD_DBUS_INTERFACE "org.freedesktop.ModemManager.Modem.Gsm.Ussd"
+
+typedef struct _MMModemGsmUssd MMModemGsmUssd;
+
+struct _MMModemGsmUssd {
+ GTypeInterface g_iface;
+
+ /* Methods */
+ void (*initiate) (MMModemGsmUssd *modem,
+ const char *command,
+ MMModemStringFn callback,
+ gpointer user_data);
+
+ void (*respond) (MMModemGsmUssd *modem,
+ const char *command,
+ MMModemStringFn callback,
+ gpointer user_data);
+
+ void (*cancel) (MMModemGsmUssd *modem,
+ MMModemFn callback,
+ gpointer user_data);
+};
+
+GType mm_modem_gsm_ussd_get_type (void);
+
+void mm_modem_gsm_ussd_initiate (MMModemGsmUssd *self,
+ const char *command,
+ MMModemStringFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_ussd_respond (MMModemGsmUssd *self,
+ const char *command,
+ MMModemStringFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_ussd_cancel (MMModemGsmUssd *self,
+ MMModemFn callback,
+ gpointer user_data);
+
+#endif /* MM_MODEM_GSM_USSD_H */
diff --git a/src/mm-modem-gsm.h b/src/mm-modem-gsm.h
index 6d9135a..a427d35 100644
--- a/src/mm-modem-gsm.h
+++ b/src/mm-modem-gsm.h
@@ -54,8 +54,9 @@ typedef enum {
MM_MODEM_GSM_ACCESS_TECH_HSDPA = 6, /* UTRAN w/HSDPA */
MM_MODEM_GSM_ACCESS_TECH_HSUPA = 7, /* UTRAN w/HSUPA */
MM_MODEM_GSM_ACCESS_TECH_HSPA = 8, /* UTRAN w/HSDPA and HSUPA */
+ MM_MODEM_GSM_ACCESS_TECH_HSPA_PLUS = 9, /* UTRAN w/HSPA+ */
- MM_MODEM_GSM_ACCESS_TECH_LAST = MM_MODEM_GSM_ACCESS_TECH_HSPA
+ MM_MODEM_GSM_ACCESS_TECH_LAST = MM_MODEM_GSM_ACCESS_TECH_HSPA_PLUS
} MMModemGsmAccessTech;
typedef enum {
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
index 6e76f46..f13b4f2 100644
--- a/src/mm-modem-helpers.c
+++ b/src/mm-modem-helpers.c
@@ -15,6 +15,8 @@
*/
#include <config.h>
+#include <stdio.h>
+#include <ctype.h>
#include <glib.h>
#include <string.h>
#include <ctype.h>
@@ -23,6 +25,7 @@
#include "mm-errors.h"
#include "mm-modem-helpers.h"
+#include "mm-log.h"
const char *
mm_strip_tag (const char *str, const char *cmd)
@@ -240,10 +243,14 @@ mm_gsm_destroy_scan_data (gpointer data)
/* +CREG: <n>,<stat>,<lac>,<ci>,<AcT> (ETSI 27.007 solicited and some CREG=2 unsolicited) */
#define CREG6 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*(\\d{1,2})"
+/* +CREG: <n>,<stat>,<lac>,<ci>,<AcT?>,<something> (Samsung Wave S8500) */
+/* '<CR><LF>+CREG: 2,1,000B,2816, B, C2816<CR><LF><CR><LF>OK<CR><LF>' */
+#define CREG7 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*[^,\\s]*"
+
GPtrArray *
mm_gsm_creg_regex_get (gboolean solicited)
{
- GPtrArray *array = g_ptr_array_sized_new (6);
+ GPtrArray *array = g_ptr_array_sized_new (7);
GRegex *regex;
/* #1 */
@@ -294,6 +301,14 @@ mm_gsm_creg_regex_get (gboolean solicited)
g_assert (regex);
g_ptr_array_add (array, regex);
+ /* #7 */
+ if (solicited)
+ regex = g_regex_new (CREG7 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ else
+ regex = g_regex_new ("\\r\\n" CREG7 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_assert (regex);
+ g_ptr_array_add (array, regex);
+
return array;
}
@@ -783,7 +798,9 @@ mm_gsm_string_to_access_tech (const char *string)
/* Better technologies are listed first since modems sometimes say
* stuff like "GPRS/EDGE" and that should be handled as EDGE.
*/
- if (strcasestr (string, "HSPA"))
+ if (strcasestr (string, "HSPA+"))
+ return MM_MODEM_GSM_ACCESS_TECH_HSPA_PLUS;
+ else if (strcasestr (string, "HSPA"))
return MM_MODEM_GSM_ACCESS_TECH_HSPA;
else if (strcasestr (string, "HSDPA/HSUPA"))
return MM_MODEM_GSM_ACCESS_TECH_HSPA;
@@ -803,3 +820,278 @@ mm_gsm_string_to_access_tech (const char *string)
return MM_MODEM_GSM_ACCESS_TECH_UNKNOWN;
}
+/*************************************************************************/
+
+char *
+mm_create_device_identifier (guint vid,
+ guint pid,
+ const char *ati,
+ const char *ati1,
+ const char *gsn,
+ const char *revision,
+ const char *model,
+ const char *manf)
+{
+ GString *devid, *msg = NULL;
+ GChecksum *sum;
+ char *p, *ret = NULL;
+ char str_vid[10], str_pid[10];
+
+ /* Build up the device identifier */
+ devid = g_string_sized_new (50);
+ if (ati)
+ g_string_append (devid, ati);
+ if (ati1) {
+ /* Only append "ATI1" if it's differnet than "ATI" */
+ if (!ati || (strcmp (ati, ati1) != 0))
+ g_string_append (devid, ati1);
+ }
+ if (gsn)
+ g_string_append (devid, gsn);
+ if (revision)
+ g_string_append (devid, revision);
+ if (model)
+ g_string_append (devid, model);
+ if (manf)
+ g_string_append (devid, manf);
+
+ if (!strlen (devid->str))
+ return NULL;
+
+ p = devid->str;
+ msg = g_string_sized_new (strlen (devid->str) + 17);
+
+ sum = g_checksum_new (G_CHECKSUM_SHA1);
+
+ if (vid) {
+ snprintf (str_vid, sizeof (str_vid) - 1, "%08x", vid);
+ g_checksum_update (sum, (const guchar *) &str_vid[0], strlen (str_vid));
+ g_string_append_printf (msg, "%08x", vid);
+ }
+ if (vid) {
+ snprintf (str_pid, sizeof (str_pid) - 1, "%08x", pid);
+ g_checksum_update (sum, (const guchar *) &str_pid[0], strlen (str_pid));
+ g_string_append_printf (msg, "%08x", pid);
+ }
+
+ while (*p) {
+ /* Strip spaces and linebreaks */
+ if (!isblank (*p) && !isspace (*p) && isascii (*p)) {
+ g_checksum_update (sum, (const guchar *) p, 1);
+ g_string_append_c (msg, *p);
+ }
+ p++;
+ }
+ ret = g_strdup (g_checksum_get_string (sum));
+ g_checksum_free (sum);
+
+ mm_dbg ("Device ID source '%s'", msg->str);
+ mm_dbg ("Device ID '%s'", ret);
+ g_string_free (msg, TRUE);
+
+ return ret;
+}
+
+/*************************************************************************/
+
+struct CindResponse {
+ char *desc;
+ guint idx;
+ gint min;
+ gint max;
+};
+
+static CindResponse *
+cind_response_new (const char *desc, guint idx, gint min, gint max)
+{
+ CindResponse *r;
+ char *p;
+
+ g_return_val_if_fail (desc != NULL, NULL);
+ g_return_val_if_fail (idx >= 0, NULL);
+
+ r = g_malloc0 (sizeof (CindResponse));
+
+ /* Strip quotes */
+ r->desc = p = g_malloc0 (strlen (desc) + 1);
+ while (*desc) {
+ if (*desc != '"' && !isspace (*desc))
+ *p++ = tolower (*desc);
+ desc++;
+ }
+
+ r->idx = idx;
+ r->max = max;
+ r->min = min;
+ return r;
+}
+
+static void
+cind_response_free (CindResponse *r)
+{
+ g_return_if_fail (r != NULL);
+
+ g_free (r->desc);
+ memset (r, 0, sizeof (CindResponse));
+ g_free (r);
+}
+
+const char *
+cind_response_get_desc (CindResponse *r)
+{
+ g_return_val_if_fail (r != NULL, NULL);
+
+ return r->desc;
+}
+
+guint
+cind_response_get_index (CindResponse *r)
+{
+ g_return_val_if_fail (r != NULL, 0);
+
+ return r->idx;
+}
+
+gint
+cind_response_get_min (CindResponse *r)
+{
+ g_return_val_if_fail (r != NULL, -1);
+
+ return r->min;
+}
+
+gint
+cind_response_get_max (CindResponse *r)
+{
+ g_return_val_if_fail (r != NULL, -1);
+
+ return r->max;
+}
+
+#define CIND_TAG "+CIND:"
+
+GHashTable *
+mm_parse_cind_test_response (const char *reply, GError **error)
+{
+ GHashTable *hash;
+ GRegex *r;
+ GMatchInfo *match_info;
+ guint idx = 1;
+
+ g_return_val_if_fail (reply != NULL, NULL);
+
+ /* Strip whitespace and response tag */
+ if (g_str_has_prefix (reply, CIND_TAG))
+ reply += strlen (CIND_TAG);
+ while (isspace (*reply))
+ reply++;
+
+ r = g_regex_new ("\\(([^,]*),\\((\\d+)[-,](\\d+)\\)", G_REGEX_UNGREEDY, 0, NULL);
+ if (!r) {
+ g_set_error_literal (error,
+ MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Could not parse scan results.");
+ return NULL;
+ }
+
+ hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) cind_response_free);
+
+ if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) {
+ while (g_match_info_matches (match_info)) {
+ CindResponse *resp;
+ char *desc, *tmp;
+ gint min = 0, max = 0;
+
+ desc = g_match_info_fetch (match_info, 1);
+
+ tmp = g_match_info_fetch (match_info, 2);
+ min = atoi (tmp);
+ g_free (tmp);
+
+ tmp = g_match_info_fetch (match_info, 3);
+ max = atoi (tmp);
+ g_free (tmp);
+
+ resp = cind_response_new (desc, idx++, min, max);
+ if (resp)
+ g_hash_table_insert (hash, g_strdup (resp->desc), resp);
+
+ g_free (desc);
+
+ g_match_info_next (match_info, NULL);
+ }
+ g_match_info_free (match_info);
+ }
+ g_regex_unref (r);
+
+ return hash;
+}
+
+GByteArray *
+mm_parse_cind_query_response(const char *reply, GError **error)
+{
+ GByteArray *array = NULL;
+ const char *p = reply;
+ GRegex *r = NULL;
+ GMatchInfo *match_info;
+ guint8 t = 0;
+
+ g_return_val_if_fail (reply != NULL, NULL);
+
+ if (!g_str_has_prefix (p, CIND_TAG)) {
+ g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Could not parse the +CIND response");
+ return NULL;
+ }
+
+ p += strlen (CIND_TAG);
+ while (isspace (*p))
+ p++;
+
+ r = g_regex_new ("(\\d+)[^0-9]+", G_REGEX_UNGREEDY, 0, NULL);
+ if (!r) {
+ g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Internal failure attempting to parse +CIND response");
+ return NULL;
+ }
+
+ if (!g_regex_match_full (r, p, strlen (p), 0, 0, &match_info, NULL)) {
+ g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Failure parsing the +CIND response");
+ goto done;
+ }
+
+ array = g_byte_array_sized_new (g_match_info_get_match_count (match_info));
+
+ /* Add a zero element so callers can use 1-based indexes returned by
+ * cind_response_get_index().
+ */
+ g_byte_array_append (array, &t, 1);
+
+ while (g_match_info_matches (match_info)) {
+ char *str;
+ gulong val;
+
+ str = g_match_info_fetch (match_info, 1);
+
+ errno = 0;
+ val = strtoul (str, NULL, 10);
+
+ t = 0;
+ if ((errno == 0) && (val < 255))
+ t = (guint8) val;
+ /* FIXME: indicate errors somehow? */
+ g_byte_array_append (array, &t, 1);
+
+ g_free (str);
+ g_match_info_next (match_info, NULL);
+ }
+ g_match_info_free (match_info);
+
+done:
+ if (r)
+ g_regex_unref (r);
+
+ return array;
+}
+
diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h
index fb100bc..71ccaa5 100644
--- a/src/mm-modem-helpers.h
+++ b/src/mm-modem-helpers.h
@@ -59,5 +59,23 @@ gboolean mm_gsm_parse_cscs_support_response (const char *reply,
MMModemGsmAccessTech mm_gsm_string_to_access_tech (const char *string);
+char *mm_create_device_identifier (guint vid,
+ guint pid,
+ const char *ati,
+ const char *ati1,
+ const char *gsn,
+ const char *revision,
+ const char *model,
+ const char *manf);
+
+typedef struct CindResponse CindResponse;
+GHashTable *mm_parse_cind_test_response (const char *reply, GError **error);
+const char *cind_response_get_desc (CindResponse *r);
+guint cind_response_get_index (CindResponse *r);
+gint cind_response_get_min (CindResponse *r);
+gint cind_response_get_max (CindResponse *r);
+
+GByteArray *mm_parse_cind_query_response(const char *reply, GError **error);
+
#endif /* MM_MODEM_HELPERS_H */
diff --git a/src/mm-modem-location.c b/src/mm-modem-location.c
index 0018295..2dadd4e 100644
--- a/src/mm-modem-location.c
+++ b/src/mm-modem-location.c
@@ -257,7 +257,7 @@ mm_modem_location_init (gpointer g_iface)
g_param_spec_boxed (MM_MODEM_LOCATION_LOCATION,
"Location",
"Available location information",
- G_TYPE_HASH_TABLE,
+ MM_MODEM_LOCATION_PROP_TYPE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_interface_install_property
@@ -267,7 +267,8 @@ mm_modem_location_init (gpointer g_iface)
"Supported location information methods",
MM_MODEM_LOCATION_CAPABILITY_UNKNOWN,
MM_MODEM_LOCATION_CAPABILITY_GPS_NMEA
- | MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI,
+ | MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI
+ | MM_MODEM_LOCATION_CAPABILITY_GPS_RAW,
MM_MODEM_LOCATION_CAPABILITY_UNKNOWN,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
diff --git a/src/mm-modem-location.h b/src/mm-modem-location.h
index dd622f1..7b8399f 100644
--- a/src/mm-modem-location.h
+++ b/src/mm-modem-location.h
@@ -23,6 +23,10 @@
#define MM_IS_MODEM_LOCATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_LOCATION))
#define MM_MODEM_LOCATION_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_LOCATION, MMModemLocation))
+#define MM_MODEM_LOCATION_DBUS_INTERFACE "org.freedesktop.ModemManager.Modem.Location"
+
+#define MM_MODEM_LOCATION_PROP_TYPE (dbus_g_type_get_map ("GHashTable", G_TYPE_UINT, G_TYPE_VALUE))
+
#define MM_MODEM_LOCATION_CAPABILITIES "location-capabilities"
#define MM_MODEM_LOCATION_ENABLED "location-enabled"
#define MM_MODEM_LOCATION_SIGNALS_LOCATION "signals-location"
@@ -32,8 +36,9 @@ typedef enum {
MM_MODEM_LOCATION_CAPABILITY_UNKNOWN = 0x00000000,
MM_MODEM_LOCATION_CAPABILITY_GPS_NMEA = 0x00000001,
MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI = 0x00000002,
+ MM_MODEM_LOCATION_CAPABILITY_GPS_RAW = 0x00000004,
- MM_MODEM_LOCATION_CAPABILITY_LAST = MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI
+ MM_MODEM_LOCATION_CAPABILITY_LAST = MM_MODEM_LOCATION_CAPABILITY_GPS_RAW
} MMModemLocationCapabilities;
typedef struct _MMModemLocation MMModemLocation;
diff --git a/src/mm-modem.c b/src/mm-modem.c
index 221c9ea..f823472 100644
--- a/src/mm-modem.c
+++ b/src/mm-modem.c
@@ -18,7 +18,7 @@
#include <string.h>
#include <dbus/dbus-glib.h>
#include "mm-modem.h"
-#include "mm-options.h"
+#include "mm-log.h"
#include "mm-errors.h"
#include "mm-callback-info.h"
#include "mm-marshal.h"
@@ -28,6 +28,7 @@ static void impl_modem_connect (MMModem *modem, const char *number, DBusGMethodI
static void impl_modem_disconnect (MMModem *modem, DBusGMethodInvocation *context);
static void impl_modem_get_ip4_config (MMModem *modem, DBusGMethodInvocation *context);
static void impl_modem_get_info (MMModem *modem, DBusGMethodInvocation *context);
+static void impl_modem_reset (MMModem *modem, DBusGMethodInvocation *context);
static void impl_modem_factory_reset (MMModem *modem, const char *code, DBusGMethodInvocation *context);
#include "mm-modem-glue.h"
@@ -478,6 +479,56 @@ impl_modem_get_info (MMModem *modem,
/*****************************************************************************/
static void
+reset_auth_cb (MMAuthRequest *req,
+ GObject *owner,
+ DBusGMethodInvocation *context,
+ gpointer user_data)
+{
+ MMModem *self = MM_MODEM (owner);
+ GError *error = NULL;
+
+ /* Return any authorization error, otherwise try to reset the modem */
+ if (!mm_modem_auth_finish (self, req, &error)) {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ } else
+ mm_modem_reset (self, async_call_done, context);
+}
+
+static void
+impl_modem_reset (MMModem *modem, DBusGMethodInvocation *context)
+{
+ GError *error = NULL;
+
+ /* Make sure the caller is authorized to reset the device */
+ if (!mm_modem_auth_request (MM_MODEM (modem),
+ MM_AUTHORIZATION_DEVICE_CONTROL,
+ context,
+ reset_auth_cb,
+ NULL, NULL,
+ &error)) {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+}
+
+void
+mm_modem_reset (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GET_INTERFACE (self)->reset)
+ MM_MODEM_GET_INTERFACE (self)->reset (self, callback, user_data);
+ else
+ async_op_not_supported (self, callback, user_data);
+}
+
+/*****************************************************************************/
+
+static void
factory_reset_auth_cb (MMAuthRequest *req,
GObject *owner,
DBusGMethodInvocation *context,
@@ -704,22 +755,10 @@ mm_modem_set_state (MMModem *self,
dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG);
if (dbus_path) {
- if (mm_options_debug ()) {
- GTimeVal tv;
-
- g_get_current_time (&tv);
- g_debug ("<%ld.%ld> Modem %s: state changed (%s -> %s)",
- tv.tv_sec,
- tv.tv_usec,
- dbus_path,
- state_to_string (old_state),
- state_to_string (new_state));
- } else {
- g_message ("Modem %s: state changed (%s -> %s)",
- dbus_path,
- state_to_string (old_state),
- state_to_string (new_state));
- }
+ mm_info ("Modem %s: state changed (%s -> %s)",
+ dbus_path,
+ state_to_string (old_state),
+ state_to_string (new_state));
}
}
}
@@ -832,7 +871,7 @@ mm_modem_init (gpointer g_iface)
MM_MODEM_IP_METHOD_PPP,
MM_MODEM_IP_METHOD_DHCP,
MM_MODEM_IP_METHOD_PPP,
- G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ G_PARAM_READWRITE));
g_object_interface_install_property
(g_iface,
@@ -870,6 +909,14 @@ mm_modem_init (gpointer g_iface)
g_object_interface_install_property
(g_iface,
+ g_param_spec_string (MM_MODEM_DEVICE_IDENTIFIER,
+ "DeviceIdentifier",
+ "A best-effort identifer of the device",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_interface_install_property
+ (g_iface,
g_param_spec_string (MM_MODEM_UNLOCK_REQUIRED,
"UnlockRequired",
"Whether or not the modem requires an unlock "
@@ -885,6 +932,22 @@ mm_modem_init (gpointer g_iface)
0, G_MAXUINT32, 0,
G_PARAM_READABLE));
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_uint (MM_MODEM_HW_VID,
+ "Hardware vendor ID",
+ "Hardware vendor ID",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_uint (MM_MODEM_HW_PID,
+ "Hardware product ID",
+ "Hardware product ID",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
/* Signals */
g_signal_new ("state-changed",
iface_type,
diff --git a/src/mm-modem.h b/src/mm-modem.h
index 0915180..7f0bf58 100644
--- a/src/mm-modem.h
+++ b/src/mm-modem.h
@@ -11,7 +11,7 @@
* GNU General Public License for more details:
*
* Copyright (C) 2008 - 2009 Novell, Inc.
- * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
*/
#ifndef MM_MODEM_H
@@ -40,7 +40,9 @@ typedef enum {
} MMModemState;
typedef enum {
- MM_MODEM_STATE_REASON_NONE = 0
+ MM_MODEM_STATE_REASON_NONE = 0,
+ MM_MODEM_STATE_REASON_USER_REQUESTED,
+ MM_MODEM_STATE_REASON_SUSPEND
} MMModemStateReason;
#define DBUS_PATH_TAG "dbus-path"
@@ -59,11 +61,14 @@ typedef enum {
#define MM_MODEM_IP_METHOD "ip-method"
#define MM_MODEM_ENABLED "enabled"
#define MM_MODEM_EQUIPMENT_IDENTIFIER "equipment-identifier"
+#define MM_MODEM_DEVICE_IDENTIFIER "device-identifier"
#define MM_MODEM_UNLOCK_REQUIRED "unlock-required"
#define MM_MODEM_UNLOCK_RETRIES "unlock-retries"
#define MM_MODEM_VALID "valid" /* not exported */
#define MM_MODEM_PLUGIN "plugin" /* not exported */
#define MM_MODEM_STATE "state" /* not exported */
+#define MM_MODEM_HW_VID "hw-vid" /* not exported */
+#define MM_MODEM_HW_PID "hw-pid" /* not exported */
#define MM_MODEM_TYPE_UNKNOWN 0
#define MM_MODEM_TYPE_GSM 1
@@ -87,7 +92,10 @@ typedef enum {
MM_MODEM_PROP_ENABLED,
MM_MODEM_PROP_EQUIPMENT_IDENTIFIER,
MM_MODEM_PROP_UNLOCK_REQUIRED,
- MM_MODEM_PROP_UNLOCK_RETRIES
+ MM_MODEM_PROP_UNLOCK_RETRIES,
+ MM_MODEM_PROP_DEVICE_IDENTIFIER,
+ MM_MODEM_PROP_HW_VID, /* Not exported */
+ MM_MODEM_PROP_HW_PID /* Not exported */
} MMModemProp;
typedef struct _MMModem MMModem;
@@ -188,6 +196,10 @@ struct _MMModem {
MMAuthRequest *req,
GError **error);
+ void (*reset) (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data);
+
void (*factory_reset) (MMModem *self,
const char *code,
MMModemFn callback,
@@ -251,6 +263,10 @@ void mm_modem_set_charset (MMModem *self,
MMModemFn callback,
gpointer user_data);
+void mm_modem_reset (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data);
+
void mm_modem_factory_reset (MMModem *self,
const char *code,
MMModemFn callback,
diff --git a/src/mm-options.c b/src/mm-options.c
deleted file mode 100644
index 7bbeefd..0000000
--- a/src/mm-options.c
+++ /dev/null
@@ -1,55 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details:
- *
- * Copyright (C) 2008 Novell, Inc.
- */
-
-#include <stdlib.h>
-#include <glib.h>
-#include "mm-options.h"
-
-static gboolean debug = FALSE;
-
-void
-mm_options_parse (int argc, char *argv[])
-{
- GOptionContext *opt_ctx;
- GError *error = NULL;
- GOptionEntry entries[] = {
- { "debug", 0, 0, G_OPTION_ARG_NONE, &debug, "Output to console rather than syslog", NULL },
- { NULL }
- };
-
- opt_ctx = g_option_context_new (NULL);
- g_option_context_set_summary (opt_ctx, "DBus system service to communicate with modems.");
- g_option_context_add_main_entries (opt_ctx, entries, NULL);
-
- if (!g_option_context_parse (opt_ctx, &argc, &argv, &error)) {
- g_warning ("%s\n", error->message);
- g_error_free (error);
- exit (1);
- }
-
- g_option_context_free (opt_ctx);
-}
-
-void
-mm_options_set_debug (gboolean enabled)
-{
- debug = enabled;
-}
-
-gboolean
-mm_options_debug (void)
-{
- return debug;
-}
diff --git a/src/mm-options.h b/src/mm-options.h
deleted file mode 100644
index ce33e27..0000000
--- a/src/mm-options.h
+++ /dev/null
@@ -1,23 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details:
- *
- * Copyright (C) 2008 Novell, Inc.
- */
-
-#ifndef MM_OPTIONS_H
-#define MM_OPTIONS_H
-
-void mm_options_parse (int argc, char *argv[]);
-void mm_options_set_debug (gboolean enabled);
-gboolean mm_options_debug (void);
-
-#endif /* MM_OPTIONS_H */
diff --git a/src/mm-plugin-base.c b/src/mm-plugin-base.c
index 80d0f90..8d32e2a 100644
--- a/src/mm-plugin-base.c
+++ b/src/mm-plugin-base.c
@@ -18,6 +18,8 @@
#include <stdio.h>
#include <stdlib.h>
+
+#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
@@ -34,6 +36,7 @@
#include "mm-utils.h"
#include "libqcdm/src/commands.h"
#include "libqcdm/src/utils.h"
+#include "mm-log.h"
static void plugin_init (MMPlugin *plugin_class);
@@ -48,6 +51,8 @@ G_DEFINE_TYPE_EXTENDED (MMPluginBase, mm_plugin_base, G_TYPE_OBJECT,
*/
static GHashTable *cached_caps = NULL;
+/* Virtual port corresponding to the embeded modem */
+static gchar *virtual_port[] = {"smd0", NULL};
typedef struct {
char *name;
@@ -803,9 +808,9 @@ try_open (gpointer user_data)
task_priv->full_id = g_signal_connect (task_priv->probe_port, "buffer-full",
G_CALLBACK (port_buffer_full), task);
- g_debug ("(%s): probe requested by plugin '%s'",
- g_udev_device_get_name (port),
- mm_plugin_get_name (MM_PLUGIN (task_priv->plugin)));
+ mm_dbg ("(%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 (MM_SERIAL_PORT (task_priv->probe_port), 100, TRUE, flash_done, task);
}
@@ -890,8 +895,8 @@ mm_plugin_base_get_device_ids (MMPluginBase *self,
guint16 *product)
{
MMPluginBasePrivate *priv;
- GUdevDevice *device = NULL;
- const char *vid, *pid;
+ GUdevDevice *device = NULL, *parent = NULL;
+ const char *vid = NULL, *pid = NULL, *parent_subsys;
gboolean success = FALSE;
g_return_val_if_fail (self != NULL, FALSE);
@@ -909,8 +914,37 @@ mm_plugin_base_get_device_ids (MMPluginBase *self,
if (!device)
goto out;
- vid = g_udev_device_get_property (device, "ID_VENDOR_ID");
- if (!vid || (strlen (vid) != 4))
+ parent = g_udev_device_get_parent (device);
+ if (parent) {
+ parent_subsys = g_udev_device_get_subsystem (parent);
+ if (parent_subsys) {
+ if (!strcmp (parent_subsys, "bluetooth")) {
+ /* Bluetooth devices report the VID/PID of the BT adapter here,
+ * which isn't really what we want. Just return null IDs instead.
+ */
+ success = TRUE;
+ goto out;
+ } else if (!strcmp (parent_subsys, "pcmcia")) {
+ /* For PCMCIA devices we need to grab the PCMCIA subsystem's
+ * manfid and cardid, since any IDs on the tty device itself
+ * may be from PCMCIA controller or something else.
+ */
+ vid = g_udev_device_get_sysfs_attr (parent, "manf_id");
+ pid = g_udev_device_get_sysfs_attr (parent, "card_id");
+ if (!vid || !pid)
+ goto out;
+ }
+ }
+ }
+
+ if (!vid)
+ vid = g_udev_device_get_property (device, "ID_VENDOR_ID");
+ if (!vid)
+ goto out;
+
+ if (strncmp (vid, "0x", 2) == 0)
+ vid += 2;
+ if (strlen (vid) != 4)
goto out;
if (vendor) {
@@ -918,8 +952,16 @@ mm_plugin_base_get_device_ids (MMPluginBase *self,
*vendor |= (guint16) ((utils_hex2byte (vid) & 0xFF) << 8);
}
- pid = g_udev_device_get_property (device, "ID_MODEL_ID");
- if (!pid || (strlen (pid) != 4)) {
+ if (!pid)
+ pid = g_udev_device_get_property (device, "ID_MODEL_ID");
+ if (!pid) {
+ *vendor = 0;
+ goto out;
+ }
+
+ if (strncmp (pid, "0x", 2) == 0)
+ pid += 2;
+ if (strlen (pid) != 4) {
*vendor = 0;
goto out;
}
@@ -934,6 +976,8 @@ mm_plugin_base_get_device_ids (MMPluginBase *self,
out:
if (device)
g_object_unref (device);
+ if (parent)
+ g_object_unref (parent);
return success;
}
@@ -980,6 +1024,20 @@ get_driver_name (GUdevDevice *device)
return ret;
}
+static gboolean
+device_file_exists(const char *name)
+{
+ char *devfile;
+ struct stat s;
+ int result;
+
+ devfile = g_strdup_printf ("/dev/%s", name);
+ result = stat (devfile, &s);
+ g_free (devfile);
+
+ return (0 == result) ? TRUE : FALSE;
+}
+
static MMPluginSupportsResult
supports_port (MMPlugin *plugin,
const char *subsys,
@@ -995,6 +1053,7 @@ supports_port (MMPlugin *plugin,
char *driver = NULL, *key = NULL;
MMPluginBaseSupportsTask *task;
MMPluginSupportsResult result = MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
+ int idx;
key = get_key (subsys, name);
task = g_hash_table_lookup (priv->tasks, key);
@@ -1007,6 +1066,19 @@ supports_port (MMPlugin *plugin,
if (!port)
goto out;
+ // Detect any modems accessible through the list of virtual ports
+ for (idx = 0; virtual_port[idx]; idx++) {
+ if (strcmp(name, virtual_port[idx]))
+ continue;
+ if (!device_file_exists(virtual_port[idx]))
+ continue;
+
+ task = supports_task_new (self, port, physdev_path, "virtual", callback, callback_data);
+ g_assert (task);
+ g_hash_table_insert (priv->tasks, g_strdup (key), g_object_ref (task));
+ goto find_plugin;
+ }
+
driver = get_driver_name (port);
if (!driver)
goto out;
@@ -1015,6 +1087,7 @@ supports_port (MMPlugin *plugin,
g_assert (task);
g_hash_table_insert (priv->tasks, g_strdup (key), g_object_ref (task));
+find_plugin:
result = MM_PLUGIN_BASE_GET_CLASS (self)->supports_port (self, existing, task);
if (result != MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS) {
/* If the plugin doesn't support the port at all, the supports task is
diff --git a/src/mm-port.c b/src/mm-port.c
index b2018fe..a1291d0 100644
--- a/src/mm-port.c
+++ b/src/mm-port.c
@@ -19,7 +19,7 @@
#include <string.h>
#include "mm-port.h"
-#include "mm-options.h"
+#include "mm-log.h"
G_DEFINE_TYPE (MMPort, mm_port, G_TYPE_OBJECT)
@@ -47,6 +47,20 @@ typedef struct {
/*****************************************************************************/
const char *
+mm_port_subsys_to_name (MMPortSubsys psubsys)
+{
+ switch (psubsys) {
+ case MM_PORT_SUBSYS_TTY:
+ return "tty";
+ case MM_PORT_SUBSYS_NET:
+ return "net";
+ default:
+ break;
+ }
+ return "(unknown)";
+}
+
+const char *
mm_port_type_to_name (MMPortType ptype)
{
switch (ptype) {
@@ -161,16 +175,10 @@ mm_port_set_connected (MMPort *self, gboolean connected)
if (priv->connected != connected) {
priv->connected = connected;
g_object_notify (G_OBJECT (self), MM_PORT_CONNECTED);
- if (mm_options_debug()) {
- GTimeVal tv;
-
- g_get_current_time (&tv);
- g_debug ("<%ld.%ld> (%s): port now %s",
- tv.tv_sec,
- tv.tv_usec,
- priv->device,
- connected ? "connected" : "disconnected");
- }
+
+ mm_dbg ("(%s): port now %s",
+ priv->device,
+ connected ? "connected" : "disconnected");
}
}
diff --git a/src/mm-port.h b/src/mm-port.h
index 43f78f4..4bcffd4 100644
--- a/src/mm-port.h
+++ b/src/mm-port.h
@@ -78,5 +78,7 @@ void mm_port_set_connected (MMPort *self, gboolean connected);
const char * mm_port_type_to_name (MMPortType ptype);
+const char * mm_port_subsys_to_name (MMPortSubsys psubsys);
+
#endif /* MM_PORT_H */
diff --git a/src/mm-properties-changed-signal.c b/src/mm-properties-changed-signal.c
index b7f3672..4408e80 100644
--- a/src/mm-properties-changed-signal.c
+++ b/src/mm-properties-changed-signal.c
@@ -11,7 +11,7 @@
* GNU General Public License for more details:
*
* Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2008 - 2009 Red Hat, Inc.
+ * Copyright (C) 2008 - 2010 Red Hat, Inc.
*/
#include <string.h>
@@ -20,17 +20,28 @@
#include <dbus/dbus-glib.h>
#include "mm-marshal.h"
#include "mm-properties-changed-signal.h"
+#include "mm-properties-changed-glue.h"
+#include "mm-log.h"
-#define DBUS_TYPE_G_MAP_OF_VARIANT (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE))
+#define DBUS_TYPE_G_MAP_OF_VARIANT (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE))
+#define DBUS_TYPE_G_ARRAY_OF_STRING (dbus_g_type_get_collection ("GPtrArray", G_TYPE_STRING))
-#define PC_SIGNAL_NAME "mm-properties-changed"
+#define MM_PC_SIGNAL_NAME "mm-properties-changed"
+#define DBUS_PC_SIGNAL_NAME "properties-changed"
#define MM_DBUS_PROPERTY_CHANGED "MM_DBUS_PROPERTY_CHANGED"
+/*****************************************************************************/
+
+typedef struct {
+ char *real_property;
+ char *interface;
+} ChangeInfo;
+
typedef struct {
/* Whitelist of GObject property names for which changes will be emitted
* over the bus.
*
- * Mapping of {property-name -> dbus-interface}
+ * Mapping of {property-name -> ChangeInfo}
*/
GHashTable *registered;
@@ -42,7 +53,6 @@ typedef struct {
*/
GHashTable *hash;
- gulong signal_id;
guint idle_id;
} PropertiesChangedInfo;
@@ -55,6 +65,17 @@ destroy_value (gpointer data)
g_slice_free (GValue, val);
}
+static void
+change_info_free (gpointer data)
+{
+ ChangeInfo *info = data;
+
+ g_free (info->real_property);
+ g_free (info->interface);
+ memset (info, 0, sizeof (ChangeInfo));
+ g_free (info);
+}
+
static PropertiesChangedInfo *
properties_changed_info_new (void)
{
@@ -62,7 +83,7 @@ properties_changed_info_new (void)
info = g_slice_new0 (PropertiesChangedInfo);
- info->registered = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ info->registered = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, change_info_free);
info->hash = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_hash_table_destroy);
@@ -124,20 +145,23 @@ properties_changed (gpointer data)
while (g_hash_table_iter_next (&iter, &key, &value)) {
const char *interface = (const char *) key;
GHashTable *props = (GHashTable *) value;
+ GPtrArray *ignore = g_ptr_array_new ();
#ifdef DEBUG
{
char buf[2048] = { 0, };
g_hash_table_foreach (props, add_to_string, &buf);
- g_message ("%s: %s -> (%s) %s", __func__,
- G_OBJECT_TYPE_NAME (object),
- interface,
- buf);
+ mm_dbg ("%s: %s -> (%s) %s", __func__,
+ G_OBJECT_TYPE_NAME (object),
+ interface,
+ buf);
}
#endif
/* Send the PropertiesChanged signal */
- g_signal_emit (object, info->signal_id, 0, interface, props);
+ g_signal_emit_by_name (object, MM_PC_SIGNAL_NAME, interface, props);
+ g_signal_emit_by_name (object, DBUS_PC_SIGNAL_NAME, interface, props, ignore);
+ g_ptr_array_free (ignore, TRUE);
}
g_hash_table_remove_all (info->hash);
@@ -191,8 +215,6 @@ get_properties_changed_info (GObject *object)
if (!info) {
info = properties_changed_info_new ();
g_object_set_data_full (object, MM_DBUS_PROPERTY_CHANGED, info, properties_changed_info_destroy);
- info->signal_id = g_signal_lookup (PC_SIGNAL_NAME, G_OBJECT_TYPE (object));
- g_assert (info->signal_id);
}
g_assert (info);
@@ -204,23 +226,23 @@ notify (GObject *object, GParamSpec *pspec)
{
GHashTable *interfaces;
PropertiesChangedInfo *info;
- const char *interface;
+ ChangeInfo *ch_info;
GValue *value;
info = get_properties_changed_info (object);
- interface = g_hash_table_lookup (info->registered, pspec->name);
- if (!interface)
+ ch_info = g_hash_table_lookup (info->registered, pspec->name);
+ if (!ch_info)
return;
/* Check if there are other changed properties for this interface already,
* otherwise create a new hash table for all changed properties for this
* D-Bus interface.
*/
- interfaces = g_hash_table_lookup (info->hash, interface);
+ interfaces = g_hash_table_lookup (info->hash, ch_info->interface);
if (!interfaces) {
interfaces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, destroy_value);
- g_hash_table_insert (info->hash, g_strdup (interface), interfaces);
+ g_hash_table_insert (info->hash, g_strdup (ch_info->interface), interfaces);
}
/* Now put the changed property value into the hash table of changed values
@@ -229,7 +251,9 @@ notify (GObject *object, GParamSpec *pspec)
value = g_slice_new0 (GValue);
g_value_init (value, pspec->value_type);
g_object_get_property (object, pspec->name, value);
- g_hash_table_insert (interfaces, uscore_to_wincaps (pspec->name), value);
+
+ /* Use real property name, which takes shadow properties into accound */
+ g_hash_table_insert (interfaces, uscore_to_wincaps (ch_info->real_property), value);
if (!info->idle_id)
info->idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, properties_changed, object, idle_id_reset);
@@ -237,11 +261,12 @@ notify (GObject *object, GParamSpec *pspec)
void
mm_properties_changed_signal_register_property (GObject *object,
- const char *property,
+ const char *gobject_property,
+ const char *real_property,
const char *interface)
{
PropertiesChangedInfo *info;
- const char *tmp;
+ ChangeInfo *ch_info;
/* All exported properties need to be registered explicitly for now since
* dbus-glib doesn't expose any method to find out the properties registered
@@ -249,28 +274,79 @@ mm_properties_changed_signal_register_property (GObject *object,
*/
info = get_properties_changed_info (object);
- tmp = g_hash_table_lookup (info->registered, property);
- if (tmp) {
+ ch_info = g_hash_table_lookup (info->registered, gobject_property);
+ if (ch_info) {
g_warning ("%s: property '%s' already registerd on interface '%s'",
- __func__, property, tmp);
- } else
- g_hash_table_insert (info->registered, g_strdup (property), g_strdup (interface));
+ __func__, gobject_property, ch_info->interface);
+ } else {
+ ch_info = g_malloc0 (sizeof (ChangeInfo));
+ ch_info->real_property = g_strdup (real_property ? real_property : gobject_property);
+ ch_info->interface = g_strdup (interface);
+ g_hash_table_insert (info->registered, g_strdup (gobject_property), ch_info);
+ }
}
-guint
-mm_properties_changed_signal_new (GObjectClass *object_class)
+void
+mm_properties_changed_signal_enable (GObjectClass *object_class)
{
- guint id;
-
object_class->notify = notify;
+}
+
+/*****************************************************************************/
- id = g_signal_new (PC_SIGNAL_NAME,
- G_OBJECT_CLASS_TYPE (object_class),
- G_SIGNAL_RUN_FIRST,
- 0, NULL, NULL,
- mm_marshal_VOID__STRING_BOXED,
- G_TYPE_NONE, 2, G_TYPE_STRING, DBUS_TYPE_G_MAP_OF_VARIANT);
+static void
+mm_properties_changed_init (gpointer g_iface)
+{
+ static gboolean initialized = FALSE;
- return id;
+ if (initialized)
+ return;
+
+ g_signal_new (MM_PC_SIGNAL_NAME,
+ G_TYPE_FROM_INTERFACE (g_iface),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ mm_marshal_VOID__STRING_BOXED,
+ G_TYPE_NONE, 2, G_TYPE_STRING, DBUS_TYPE_G_MAP_OF_VARIANT);
+
+ g_signal_new (DBUS_PC_SIGNAL_NAME,
+ G_TYPE_FROM_INTERFACE (g_iface),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ mm_marshal_VOID__STRING_BOXED_BOXED,
+ G_TYPE_NONE, 3,
+ G_TYPE_STRING,
+ DBUS_TYPE_G_MAP_OF_VARIANT,
+ DBUS_TYPE_G_ARRAY_OF_STRING);
+
+ initialized = TRUE;
}
+GType
+mm_properties_changed_get_type (void)
+{
+ static GType pc_type = 0;
+
+ if (!G_UNLIKELY (pc_type)) {
+ const GTypeInfo pc_info = {
+ sizeof (MMPropertiesChanged), /* class_size */
+ mm_properties_changed_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL
+ };
+
+ pc_type = g_type_register_static (G_TYPE_INTERFACE,
+ "MMPropertiesChanged",
+ &pc_info, 0);
+
+ g_type_interface_add_prerequisite (pc_type, G_TYPE_OBJECT);
+ dbus_g_object_type_install_info (pc_type, &dbus_glib_mm_properties_changed_object_info);
+ }
+
+ return pc_type;
+}
diff --git a/src/mm-properties-changed-signal.h b/src/mm-properties-changed-signal.h
index 60e71b9..a6d0f3e 100644
--- a/src/mm-properties-changed-signal.h
+++ b/src/mm-properties-changed-signal.h
@@ -11,7 +11,7 @@
* GNU General Public License for more details:
*
* Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2008 - 2009 Red Hat, Inc.
+ * Copyright (C) 2008 - 2010 Red Hat, Inc.
*/
#ifndef _MM_PROPERTIES_CHANGED_SIGNAL_H_
@@ -19,10 +19,22 @@
#include <glib-object.h>
-guint mm_properties_changed_signal_new (GObjectClass *object_class);
+#define MM_TYPE_PROPERTIES_CHANGED (mm_properties_changed_get_type ())
+#define MM_PROPERTIES_CHANGED(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PROPERTIES_CHANGED, MMPropertiesChanged))
+#define MM_IS_PROPERTIES_CHANGED(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PROPERTIES_CHANGED))
+#define MM_PROPERTIES_CHANGED_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_PROPERTIES_CHANGED, MMPropertiesChanged))
+
+typedef struct {
+ GTypeInterface g_iface;
+} MMPropertiesChanged;
+
+GType mm_properties_changed_get_type (void);
+
+void mm_properties_changed_signal_enable (GObjectClass *object_class);
void mm_properties_changed_signal_register_property (GObject *object,
- const char *property,
+ const char *gobject_property,
+ const char *real_property,
const char *interface);
#endif /* _MM_PROPERTIES_CHANGED_SIGNAL_H_ */
diff --git a/src/mm-qcdm-serial-port.c b/src/mm-qcdm-serial-port.c
index 1ce27e7..e467f2a 100644
--- a/src/mm-qcdm-serial-port.c
+++ b/src/mm-qcdm-serial-port.c
@@ -21,9 +21,9 @@
#include "mm-qcdm-serial-port.h"
#include "mm-errors.h"
-#include "mm-options.h"
#include "libqcdm/src/com.h"
#include "libqcdm/src/utils.h"
+#include "mm-log.h"
G_DEFINE_TYPE (MMQcdmSerialPort, mm_qcdm_serial_port, MM_TYPE_SERIAL_PORT)
@@ -37,22 +37,38 @@ typedef struct {
/*****************************************************************************/
static gboolean
-parse_response (MMSerialPort *port, GByteArray *response, GError **error)
+find_qcdm_start (GByteArray *response, gsize *start)
{
- int i;
+ int i, last = -1;
- /* Look for the QCDM packet termination character; if we found it, treat
- * the buffer as a qcdm command.
+ /* Look for 3 bytes and a QCDM frame marker, ie enough data for a valid
+ * frame. There will usually be three cases here; (1) a QCDM frame
+ * starting with data and terminated by 0x7E, and (2) a QCDM frame starting
+ * with 0x7E and ending with 0x7E, and (3) a non-QCDM frame that still
+ * uses HDLC framing (like Sierra CnS) that starts and ends with 0x7E.
*/
for (i = 0; i < response->len; i++) {
- if (response->data[i] == 0x7E)
- return TRUE;
+ if (response->data[i] == 0x7E) {
+ if (i > last + 3) {
+ /* Got a full QCDM frame; 3 non-0x7E bytes and a terminator */
+ if (start)
+ *start = last + 1;
+ return TRUE;
+ }
+
+ /* Save position of the last QCDM frame marker */
+ last = i;
+ }
}
-
- /* Otherwise, need more data from the device */
return FALSE;
}
+static gboolean
+parse_response (MMSerialPort *port, GByteArray *response, GError **error)
+{
+ return find_qcdm_start (response, NULL);
+}
+
static gsize
handle_response (MMSerialPort *port,
GByteArray *response,
@@ -64,41 +80,49 @@ handle_response (MMSerialPort *port,
GByteArray *unescaped = NULL;
GError *dm_error = NULL;
gsize used = 0;
+ gsize start = 0;
+ gboolean success = FALSE, more = FALSE;
+ gsize unescaped_len = 0;
+
+ if (error)
+ goto callback;
+
+ /* Get the offset into the buffer of where the QCDM frame starts */
+ if (!find_qcdm_start (response, &start)) {
+ g_set_error_literal (&dm_error,
+ MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Failed to parse QCDM packet.");
+ /* Discard the unparsable data */
+ used = response->len;
+ goto callback;
+ }
- /* Ignore empty frames */
- if (response->len > 0 && response->data[0] == 0x7E)
- return 1;
-
- if (!error) {
- gboolean more = FALSE, success;
- gsize unescaped_len = 0;
-
- /* FIXME: don't munge around with byte array internals */
- unescaped = g_byte_array_sized_new (1024);
- success = dm_decapsulate_buffer ((const char *) response->data,
- response->len,
- (char *) unescaped->data,
- 1024,
- &unescaped_len,
- &used,
- &more);
- if (!success) {
- g_set_error_literal (&dm_error,
- MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
- "Failed to unescape QCDM packet.");
- g_byte_array_free (unescaped, TRUE);
- unescaped = NULL;
- } else if (more) {
- /* Need more data; we shouldn't have gotten here since the parse
- * function checks for the end-of-frame marker, but whatever.
- */
- return 0;
- } else {
- /* Successfully decapsulated the DM command */
- unescaped->len = (guint) unescaped_len;
- }
+ /* FIXME: don't munge around with byte array internals */
+ unescaped = g_byte_array_sized_new (1024);
+ success = dm_decapsulate_buffer ((const char *) (response->data + start),
+ response->len - start,
+ (char *) unescaped->data,
+ 1024,
+ &unescaped_len,
+ &used,
+ &more);
+ if (!success) {
+ g_set_error_literal (&dm_error,
+ MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Failed to unescape QCDM packet.");
+ g_byte_array_free (unescaped, TRUE);
+ unescaped = NULL;
+ } else if (more) {
+ /* Need more data; we shouldn't have gotten here since the parse
+ * function checks for the end-of-frame marker, but whatever.
+ */
+ return 0;
+ } else {
+ /* Successfully decapsulated the DM command */
+ unescaped->len = (guint) unescaped_len;
}
+callback:
response_callback (MM_QCDM_SERIAL_PORT (port),
unescaped,
dm_error ? dm_error : error,
@@ -106,8 +130,9 @@ handle_response (MMSerialPort *port,
if (unescaped)
g_byte_array_free (unescaped, TRUE);
+ g_clear_error (&dm_error);
- return used;
+ return start + used;
}
/*****************************************************************************/
@@ -157,7 +182,6 @@ debug_log (MMSerialPort *port, const char *prefix, const char *buf, gsize len)
{
static GString *debug = NULL;
const char *s = buf;
- GTimeVal tv;
if (!debug)
debug = g_string_sized_new (512);
@@ -167,12 +191,7 @@ debug_log (MMSerialPort *port, const char *prefix, const char *buf, gsize len)
while (len--)
g_string_append_printf (debug, " %02x", (guint8) (*s++ & 0xFF));
- g_get_current_time (&tv);
- g_debug ("<%ld.%ld> (%s): %s",
- tv.tv_sec,
- tv.tv_usec,
- mm_port_get_device (MM_PORT (port)),
- debug->str);
+ mm_dbg ("(%s): %s", mm_port_get_device (MM_PORT (port)), debug->str);
g_string_truncate (debug, 0);
}
@@ -196,6 +215,23 @@ mm_qcdm_serial_port_new (const char *name, MMPortType ptype)
NULL));
}
+MMQcdmSerialPort *
+mm_qcdm_serial_port_new_fd (int fd, MMPortType ptype)
+{
+ MMQcdmSerialPort *port;
+ char *name;
+
+ name = g_strdup_printf ("port%d", fd);
+ port = MM_QCDM_SERIAL_PORT (g_object_new (MM_TYPE_QCDM_SERIAL_PORT,
+ MM_PORT_DEVICE, name,
+ MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY,
+ MM_PORT_TYPE, ptype,
+ MM_SERIAL_PORT_FD, fd,
+ NULL));
+ g_free (name);
+ return port;
+}
+
static void
mm_qcdm_serial_port_init (MMQcdmSerialPort *self)
{
diff --git a/src/mm-qcdm-serial-port.h b/src/mm-qcdm-serial-port.h
index e70e124..2786ee8 100644
--- a/src/mm-qcdm-serial-port.h
+++ b/src/mm-qcdm-serial-port.h
@@ -50,6 +50,8 @@ GType mm_qcdm_serial_port_get_type (void);
MMQcdmSerialPort *mm_qcdm_serial_port_new (const char *name, MMPortType ptype);
+MMQcdmSerialPort *mm_qcdm_serial_port_new_fd (int fd, MMPortType ptype);
+
void mm_qcdm_serial_port_queue_command (MMQcdmSerialPort *self,
GByteArray *command,
guint32 timeout_seconds,
diff --git a/src/mm-serial-parsers.c b/src/mm-serial-parsers.c
index 7c9598e..75bcce4 100644
--- a/src/mm-serial-parsers.c
+++ b/src/mm-serial-parsers.c
@@ -19,6 +19,7 @@
#include "mm-serial-parsers.h"
#include "mm-errors.h"
+#include "mm-log.h"
/* Clean up the response by removing control characters like <CR><LF> etc */
static void
@@ -33,6 +34,13 @@ response_clean (GString *response)
s -= 2;
}
+ /* Contains duplicate '<CR><CR>' */
+ s = response->str;
+ while ((response->len >= 2) && (*s == '\r') && (*(s + 1) == '\r')) {
+ g_string_erase (response, 0, 1);
+ s = response->str;
+ }
+
/* Starts with one or more '<CR><LF>' */
s = response->str;
while ((response->len >= 2) && (*s == '\r') && (*(s + 1) == '\n')) {
@@ -167,7 +175,7 @@ mm_serial_parser_v0_parse (gpointer data,
response_clean (response);
if (local_error) {
- g_debug ("Got failure code %d: %s", local_error->code, local_error->message);
+ mm_dbg ("Got failure code %d: %s", local_error->code, local_error->message);
g_propagate_error (error, local_error);
}
@@ -192,6 +200,7 @@ typedef struct {
GRegex *regex_connect;
GRegex *regex_cme_error;
GRegex *regex_cme_error_str;
+ GRegex *regex_ezx_error;
GRegex *regex_unknown_error;
GRegex *regex_connect_failed;
} MMSerialParserV1;
@@ -208,6 +217,7 @@ mm_serial_parser_v1_new (void)
parser->regex_connect = g_regex_new ("\\r\\nCONNECT.*\\r\\n", flags, 0, NULL);
parser->regex_cme_error = g_regex_new ("\\r\\n\\+CME ERROR: (\\d+)\\r\\n$", flags, 0, NULL);
parser->regex_cme_error_str = g_regex_new ("\\r\\n\\+CME ERROR: ([^\\n\\r]+)\\r\\n$", flags, 0, NULL);
+ parser->regex_ezx_error = g_regex_new ("\\r\\n\\MODEM 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);
parser->regex_connect_failed = g_regex_new ("\\r\\n(NO CARRIER)|(BUSY)|(NO ANSWER)|(NO DIALTONE)\\r\\n$", flags, 0, NULL);
@@ -273,6 +283,19 @@ mm_serial_parser_v1_parse (gpointer data,
goto done;
}
+ /* Motorola EZX errors */
+ found = g_regex_match_full (parser->regex_ezx_error,
+ response->str, response->len,
+ 0, 0, &match_info, NULL);
+ if (found) {
+ str = g_match_info_fetch (match_info, 1);
+ g_assert (str);
+ local_error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN);
+ g_free (str);
+ g_match_info_free (match_info);
+ goto done;
+ }
+
/* Last resort; unknown error */
found = g_regex_match_full (parser->regex_unknown_error,
response->str, response->len,
@@ -314,7 +337,7 @@ done:
response_clean (response);
if (local_error) {
- g_debug ("Got failure code %d: %s", local_error->code, local_error->message);
+ mm_dbg ("Got failure code %d: %s", local_error->code, local_error->message);
g_propagate_error (error, local_error);
}
@@ -332,6 +355,7 @@ mm_serial_parser_v1_destroy (gpointer data)
g_regex_unref (parser->regex_connect);
g_regex_unref (parser->regex_cme_error);
g_regex_unref (parser->regex_cme_error_str);
+ g_regex_unref (parser->regex_ezx_error);
g_regex_unref (parser->regex_unknown_error);
g_regex_unref (parser->regex_connect_failed);
diff --git a/src/mm-serial-port.c b/src/mm-serial-port.c
index ed44167..bf2a98a 100644
--- a/src/mm-serial-port.c
+++ b/src/mm-serial-port.c
@@ -26,10 +26,11 @@
#include <errno.h>
#include <sys/ioctl.h>
#include <string.h>
+#include <linux/serial.h>
#include "mm-serial-port.h"
#include "mm-errors.h"
-#include "mm-options.h"
+#include "mm-log.h"
static gboolean mm_serial_port_queue_process (gpointer data);
@@ -42,6 +43,7 @@ enum {
PROP_PARITY,
PROP_STOPBITS,
PROP_SEND_DELAY,
+ PROP_FD,
LAST_PROP
};
@@ -150,10 +152,10 @@ mm_serial_port_print_config (MMSerialPort *port, const char *detail)
return;
}
- g_message ("*** %s (%s): (%s) baud rate: %d (%s)",
- __func__, detail, mm_port_get_device (MM_PORT (port)),
- stbuf.c_cflag & CBAUD,
- baud_to_string (stbuf.c_cflag & CBAUD));
+ mm_info ("(%s): (%s) baud rate: %d (%s)",
+ detail, mm_port_get_device (MM_PORT (port)),
+ stbuf.c_cflag & CBAUD,
+ baud_to_string (stbuf.c_cflag & CBAUD));
}
#endif
@@ -348,7 +350,7 @@ serial_debug (MMSerialPort *self, const char *prefix, const char *buf, gsize len
{
g_return_if_fail (len > 0);
- if (mm_options_debug () && MM_SERIAL_PORT_GET_CLASS (self)->debug_log)
+ if (MM_SERIAL_PORT_GET_CLASS (self)->debug_log)
MM_SERIAL_PORT_GET_CLASS (self)->debug_log (self, prefix, buf, len);
}
@@ -684,6 +686,7 @@ mm_serial_port_open (MMSerialPort *self, GError **error)
MMSerialPortPrivate *priv;
char *devfile;
const char *device;
+ struct serial_struct sinfo;
g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE);
@@ -696,11 +699,15 @@ mm_serial_port_open (MMSerialPort *self, GError **error)
goto success;
}
- 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);
+ mm_info ("(%s) opening serial port...", device);
+
+ /* Only open a new file descriptor if we weren't given one already */
+ if (priv->fd < 0) {
+ 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) {
/* nozomi isn't ready yet when the port appears, and it'll return
@@ -733,6 +740,15 @@ mm_serial_port_open (MMSerialPort *self, GError **error)
if (!MM_SERIAL_PORT_GET_CLASS (self)->config_fd (self, priv->fd, error))
goto error;
+ /* Don't wait for pending data when closing the port; this can cause some
+ * stupid devices that don't respond to URBs on a particular port to hang
+ * for 30 seconds when probin fails.
+ */
+ if (ioctl (priv->fd, TIOCGSERIAL, &sinfo) == 0) {
+ sinfo.closing_wait = ASYNC_CLOSING_WAIT_NONE;
+ ioctl (priv->fd, TIOCSSERIAL, &sinfo);
+ }
+
priv->channel = g_io_channel_unix_new (priv->fd);
g_io_channel_set_encoding (priv->channel, NULL, NULL);
priv->watch_id = g_io_add_watch (priv->channel,
@@ -745,13 +761,7 @@ mm_serial_port_open (MMSerialPort *self, GError **error)
success:
priv->open_count++;
- if (mm_options_debug ()) {
- GTimeVal tv;
-
- g_get_current_time (&tv);
- g_debug ("<%ld.%ld> (%s) device open count is %d (open)",
- tv.tv_sec, tv.tv_usec, device, priv->open_count);
- }
+ mm_dbg ("(%s) device open count is %d (open)", device, priv->open_count);
return TRUE;
error:
@@ -785,13 +795,7 @@ mm_serial_port_close (MMSerialPort *self)
priv->open_count--;
- if (mm_options_debug ()) {
- GTimeVal tv;
-
- g_get_current_time (&tv);
- g_debug ("<%ld.%ld> (%s) device open count is %d (close)",
- tv.tv_sec, tv.tv_usec, device, priv->open_count);
- }
+ mm_dbg ("(%s) device open count is %d (close)", device, priv->open_count);
if (priv->open_count > 0)
return;
@@ -802,7 +806,9 @@ mm_serial_port_close (MMSerialPort *self)
}
if (priv->fd >= 0) {
- g_message ("(%s) closing serial device...", device);
+ GTimeVal tv_start, tv_end;
+
+ mm_info ("(%s) closing serial port...", device);
mm_port_set_connected (MM_PORT (self), FALSE);
@@ -816,9 +822,24 @@ mm_serial_port_close (MMSerialPort *self)
mm_serial_port_flash_cancel (self);
+ g_get_current_time (&tv_start);
+
tcsetattr (priv->fd, TCSANOW, &priv->old_t);
+ tcflush (priv->fd, TCIOFLUSH);
close (priv->fd);
priv->fd = -1;
+
+ g_get_current_time (&tv_end);
+
+ mm_info ("(%s) serial port closed", device);
+
+ /* Some ports don't respond to data and when close is called
+ * the serial layer waits up to 30 second (closing_wait) for
+ * that data to send before giving up and returning from close().
+ * Log that. See GNOME bug #630670 for more details.
+ */
+ if (tv_end.tv_sec - tv_start.tv_sec > 20)
+ mm_warn ("(%s): close blocked by driver for more than 20 seconds!", device);
}
/* Clear the command queue */
@@ -1185,6 +1206,9 @@ set_property (GObject *object, guint prop_id,
MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (object);
switch (prop_id) {
+ case PROP_FD:
+ priv->fd = g_value_get_int (value);
+ break;
case PROP_BAUD:
priv->baud = g_value_get_uint (value);
break;
@@ -1213,6 +1237,9 @@ get_property (GObject *object, guint prop_id,
MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (object);
switch (prop_id) {
+ case PROP_FD:
+ g_value_set_int (value, priv->fd);
+ break;
case PROP_BAUD:
g_value_set_uint (value, priv->baud);
break;
@@ -1276,6 +1303,14 @@ mm_serial_port_class_init (MMSerialPortClass *klass)
/* Properties */
g_object_class_install_property
+ (object_class, PROP_FD,
+ g_param_spec_int (MM_SERIAL_PORT_FD,
+ "File descriptor",
+ "Fiel descriptor",
+ -1, G_MAXINT, -1,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property
(object_class, PROP_BAUD,
g_param_spec_uint (MM_SERIAL_PORT_BAUD,
"Baud",
diff --git a/src/mm-serial-port.h b/src/mm-serial-port.h
index f78f793..5fee1b4 100644
--- a/src/mm-serial-port.h
+++ b/src/mm-serial-port.h
@@ -35,6 +35,7 @@
#define MM_SERIAL_PORT_PARITY "parity"
#define MM_SERIAL_PORT_STOPBITS "stopbits"
#define MM_SERIAL_PORT_SEND_DELAY "send-delay"
+#define MM_SERIAL_PORT_FD "fd" /* Construct-only */
typedef struct _MMSerialPort MMSerialPort;
typedef struct _MMSerialPortClass MMSerialPortClass;
diff --git a/src/mm-utils.c b/src/mm-utils.c
index 56182c0..c435d1d 100644
--- a/src/mm-utils.c
+++ b/src/mm-utils.c
@@ -76,3 +76,17 @@ utils_hexstr2bin (const char *hex, gsize *out_len)
/* End from hostap */
+char *
+utils_bin2hexstr (const guint8 *bin, gsize len)
+{
+ GString *ret;
+ gsize i;
+
+ g_return_val_if_fail (bin != NULL, NULL);
+
+ ret = g_string_sized_new (len * 2 + 1);
+ for (i = 0; i < len; i++)
+ g_string_append_printf (ret, "%.2X", bin[i]);
+ return g_string_free (ret, FALSE);
+}
+
diff --git a/src/mm-utils.h b/src/mm-utils.h
index 79e7827..1b9b328 100644
--- a/src/mm-utils.h
+++ b/src/mm-utils.h
@@ -20,5 +20,7 @@ int utils_hex2byte (const char *hex);
char *utils_hexstr2bin (const char *hex, gsize *out_len);
+char *utils_bin2hexstr (const guint8 *bin, gsize len);
+
#endif /* MM_UTILS_H */
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index 74255db..e265bc1 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -1,7 +1,10 @@
INCLUDES = \
-I$(top_srcdir)/src
-noinst_PROGRAMS = test-modem-helpers
+noinst_PROGRAMS = \
+ test-modem-helpers \
+ test-charsets \
+ test-qcdm-serial-port
test_modem_helpers_SOURCES = \
test-modem-helpers.c
@@ -13,10 +16,36 @@ test_modem_helpers_LDADD = \
$(top_builddir)/src/libmodem-helpers.la \
$(MM_LIBS)
+test_charsets_SOURCES = \
+ test-charsets.c
+
+test_charsets_CPPFLAGS = \
+ $(MM_CFLAGS)
+
+test_charsets_LDADD = \
+ $(top_builddir)/src/libmodem-helpers.la \
+ $(MM_LIBS)
+
+test_qcdm_serial_port_SOURCES = \
+ test-qcdm-serial-port.c
+
+test_qcdm_serial_port_CPPFLAGS = \
+ $(MM_CFLAGS) \
+ -I$(top_srcdir)
+
+test_qcdm_serial_port_LDADD = \
+ $(MM_LIBS) \
+ $(top_builddir)/src/libserial.la \
+ $(top_builddir)/src/libmodem-helpers.la \
+ $(top_builddir)/libqcdm/src/libqcdm.la \
+ -lutil
+
if WITH_TESTS
check-local: test-modem-helpers
$(abs_builddir)/test-modem-helpers
+ $(abs_builddir)/test-charsets
+ $(abs_builddir)/test-qcdm-serial-port
endif
diff --git a/src/tests/test-charsets.c b/src/tests/test-charsets.c
new file mode 100644
index 0000000..656b80c
--- /dev/null
+++ b/src/tests/test-charsets.c
@@ -0,0 +1,323 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ */
+
+#include <glib.h>
+#include <string.h>
+
+#include "mm-modem-helpers.h"
+
+static void
+test_def_chars (void *f, gpointer d)
+{
+ /* Test that a string with all the characters in the GSM 03.38 charset
+ * are converted from UTF-8 to GSM and back to UTF-8 successfully.
+ */
+ static const char *s = "@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\"#¤%&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà";
+ guint8 *gsm, *utf8;
+ guint32 len = 0;
+
+ /* Convert to GSM */
+ gsm = mm_charset_utf8_to_unpacked_gsm (s, &len);
+ g_assert (gsm);
+ g_assert_cmpint (len, ==, 127);
+
+ /* And back to UTF-8 */
+ utf8 = mm_charset_gsm_unpacked_to_utf8 (gsm, len);
+ g_assert (utf8);
+ g_assert_cmpstr (s, ==, (const char *) utf8);
+
+ g_free (gsm);
+ g_free (utf8);
+}
+
+static void
+test_esc_chars (void *f, gpointer d)
+{
+ /* Test that a string with all the characters in the extended GSM 03.38
+ * charset are converted from UTF-8 to GSM and back to UTF-8 successfully.
+ */
+ static const char *s = "\f^{}\\[~]|€";
+ guint8 *gsm, *utf8;
+ guint32 len = 0;
+
+ /* Convert to GSM */
+ gsm = mm_charset_utf8_to_unpacked_gsm (s, &len);
+ g_assert (gsm);
+ g_assert_cmpint (len, ==, 20);
+
+ /* And back to UTF-8 */
+ utf8 = mm_charset_gsm_unpacked_to_utf8 (gsm, len);
+ g_assert (utf8);
+ g_assert_cmpstr (s, ==, (const char *) utf8);
+
+ g_free (gsm);
+ g_free (utf8);
+}
+
+static void
+test_mixed_chars (void *f, gpointer d)
+{
+ /* Test that a string with a mix of GSM 03.38 default and extended characters
+ * is converted from UTF-8 to GSM and back to UTF-8 successfully.
+ */
+ static const char *s = "@£$¥èéùìø\fΩΠΨΣΘ{ΞÆæß(})789\\:;<=>[?¡QRS]TUÖ|Ñܧ¿abpqrstuvöñüà€";
+ guint8 *gsm, *utf8;
+ guint32 len = 0;
+
+ /* Convert to GSM */
+ gsm = mm_charset_utf8_to_unpacked_gsm (s, &len);
+ g_assert (gsm);
+ g_assert_cmpint (len, ==, 69);
+
+ /* And back to UTF-8 */
+ utf8 = mm_charset_gsm_unpacked_to_utf8 (gsm, len);
+ g_assert (utf8);
+ g_assert_cmpstr (s, ==, (const char *) utf8);
+
+ g_free (gsm);
+ g_free (utf8);
+}
+
+static void
+test_unpack_gsm7 (void *f, gpointer d)
+{
+ static const guint8 gsm[] = { 0xC8, 0xF7, 0x1D, 0x14, 0x96, 0x97, 0x41, 0xF9, 0x77, 0xFD, 0x07 };
+ static const guint8 expected[] = { 0x48, 0x6f, 0x77, 0x20, 0x61, 0x72, 0x65, 0x20, 0x79, 0x6f, 0x75, 0x3f };
+ guint8 *unpacked;
+ guint32 unpacked_len = 0;
+
+ unpacked = gsm_unpack (gsm, sizeof (gsm), 0, &unpacked_len);
+ g_assert (unpacked);
+ g_assert_cmpint (unpacked_len, ==, sizeof (expected));
+ g_assert_cmpint (memcmp (unpacked, expected, unpacked_len), ==, 0);
+
+ g_free (unpacked);
+}
+
+static void
+test_unpack_gsm7_7_chars (void *f, gpointer d)
+{
+ static const guint8 gsm[] = { 0xF1, 0x7B, 0x59, 0x4E, 0xCF, 0xD7, 0x01 };
+ static const guint8 expected[] = { 0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x00 };
+ guint8 *unpacked;
+ guint32 unpacked_len = 0;
+
+ /* Tests the edge case where there are 7 bits left in the packed
+ * buffer but those 7 bits do not contain a character. In this case
+ * we expect a trailing NULL byte and the caller must know enough about
+ * the intended message to remove it when required.
+ */
+
+ unpacked = gsm_unpack (gsm, sizeof (gsm), 0, &unpacked_len);
+ g_assert (unpacked);
+ g_assert_cmpint (unpacked_len, ==, sizeof (expected));
+ g_assert_cmpint (memcmp (unpacked, expected, unpacked_len), ==, 0);
+
+ g_free (unpacked);
+}
+
+static void
+test_unpack_gsm7_all_chars (void *f, gpointer d)
+{
+ /* Packed array of all chars in GSM default and extended charset */
+ static const guint8 gsm[] = {
+ 0x80, 0x80, 0x60, 0x40, 0x28, 0x18, 0x0E, 0x88, 0x84, 0x62, 0xC1, 0x68,
+ 0x38, 0x1E, 0x90, 0x88, 0x64, 0x42, 0xA9, 0x58, 0x2E, 0x98, 0x8C, 0x66,
+ 0xC3, 0xE9, 0x78, 0x3E, 0xA0, 0x90, 0x68, 0x44, 0x2A, 0x99, 0x4E, 0xA8,
+ 0x94, 0x6A, 0xC5, 0x6A, 0xB9, 0x5E, 0xB0, 0x98, 0x6C, 0x46, 0xAB, 0xD9,
+ 0x6E, 0xB8, 0x9C, 0x6E, 0xC7, 0xEB, 0xF9, 0x7E, 0xC0, 0xA0, 0x70, 0x48,
+ 0x2C, 0x1A, 0x8F, 0xC8, 0xA4, 0x72, 0xC9, 0x6C, 0x3A, 0x9F, 0xD0, 0xA8,
+ 0x74, 0x4A, 0xAD, 0x5A, 0xAF, 0xD8, 0xAC, 0x76, 0xCB, 0xED, 0x7A, 0xBF,
+ 0xE0, 0xB0, 0x78, 0x4C, 0x2E, 0x9B, 0xCF, 0xE8, 0xB4, 0x7A, 0xCD, 0x6E,
+ 0xBB, 0xDF, 0xF0, 0xB8, 0x7C, 0x4E, 0xAF, 0xDB, 0xEF, 0xF8, 0xBC, 0x7E,
+ 0xCF, 0xEF, 0xFB, 0xFF, 0x1B, 0xC5, 0x86, 0xB2, 0x41, 0x6D, 0x52, 0x9B,
+ 0xD7, 0x86, 0xB7, 0xE9, 0x6D, 0x7C, 0x1B, 0xE0, 0xA6, 0x0C
+ };
+ static const guint8 ext[] = {
+ 0x1B, 0x0A, 0x1B, 0x14, 0x1B, 0x28, 0x1B, 0x29, 0x1B, 0x2F, 0x1B, 0x3C,
+ 0x1B, 0x3D, 0x1B, 0x3E, 0x1B, 0x40, 0x1B, 0x65
+ };
+ guint8 *unpacked;
+ guint32 unpacked_len = 0;
+ int i;
+
+ unpacked = gsm_unpack (gsm, sizeof (gsm), 0, &unpacked_len);
+ g_assert (unpacked);
+ g_assert_cmpint (unpacked_len, ==, 148);
+
+ /* Test default chars */
+ for (i = 0; i < 128; i++)
+ g_assert_cmpint (unpacked[i], ==, i);
+
+ /* Text extended chars */
+ g_assert_cmpint (memcmp ((guint8 *) (unpacked + 128), &ext[0], sizeof (ext)), ==, 0);
+
+ g_free (unpacked);
+}
+
+static void
+test_pack_gsm7 (void *f, gpointer d)
+{
+ static const guint8 unpacked[] = { 0x48, 0x6f, 0x77, 0x20, 0x61, 0x72, 0x65, 0x20, 0x79, 0x6f, 0x75, 0x3f };
+ static const guint8 expected[] = { 0xC8, 0xF7, 0x1D, 0x14, 0x96, 0x97, 0x41, 0xF9, 0x77, 0xFD, 0x07 };
+ guint8 *packed;
+ guint32 packed_len = 0;
+
+ packed = gsm_pack (unpacked, sizeof (unpacked), 0, &packed_len);
+ g_assert (packed);
+ g_assert_cmpint (packed_len, ==, sizeof (expected));
+ g_assert_cmpint (memcmp (packed, expected, packed_len), ==, 0);
+
+ g_free (packed);
+}
+
+static void
+test_pack_gsm7_7_chars (void *f, gpointer d)
+{
+ static const guint8 unpacked[] = { 0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75 };
+ static const guint8 expected[] = { 0xF1, 0x7B, 0x59, 0x4E, 0xCF, 0xD7, 0x01 };
+ guint8 *packed;
+ guint32 packed_len = 0;
+
+ /* Tests the edge case where there are 7 bits left in the packed
+ * buffer but those 7 bits do not contain a character. In this case
+ * we expect a trailing NULL byte and the caller must know enough about
+ * the intended message to remove it when required.
+ */
+
+ packed = gsm_pack (unpacked, sizeof (unpacked), 0, &packed_len);
+ g_assert (packed);
+ g_assert_cmpint (packed_len, ==, sizeof (expected));
+ g_assert_cmpint (memcmp (packed, expected, packed_len), ==, 0);
+
+ g_free (packed);
+}
+
+#if 0
+static void
+print_array (const guint8 *array, guint32 len)
+{
+ int col;
+ guint8 c;
+
+ g_print ("\n");
+ for (c = 0, col = 0; c < len; c++) {
+ g_print ("0x%02X, ", array[c] & 0xFF);
+ if (col++ == 11) {
+ col = 0;
+ g_print ("\n");
+ }
+ }
+ g_print ("\n");
+}
+#endif
+
+static void
+test_pack_gsm7_all_chars (void *f, gpointer d)
+{
+ /* Packed array of all chars in GSM default and extended charset */
+ static const guint8 expected[] = {
+ 0x80, 0x80, 0x60, 0x40, 0x28, 0x18, 0x0E, 0x88, 0x84, 0x62, 0xC1, 0x68,
+ 0x38, 0x1E, 0x90, 0x88, 0x64, 0x42, 0xA9, 0x58, 0x2E, 0x98, 0x8C, 0x66,
+ 0xC3, 0xE9, 0x78, 0x3E, 0xA0, 0x90, 0x68, 0x44, 0x2A, 0x99, 0x4E, 0xA8,
+ 0x94, 0x6A, 0xC5, 0x6A, 0xB9, 0x5E, 0xB0, 0x98, 0x6C, 0x46, 0xAB, 0xD9,
+ 0x6E, 0xB8, 0x9C, 0x6E, 0xC7, 0xEB, 0xF9, 0x7E, 0xC0, 0xA0, 0x70, 0x48,
+ 0x2C, 0x1A, 0x8F, 0xC8, 0xA4, 0x72, 0xC9, 0x6C, 0x3A, 0x9F, 0xD0, 0xA8,
+ 0x74, 0x4A, 0xAD, 0x5A, 0xAF, 0xD8, 0xAC, 0x76, 0xCB, 0xED, 0x7A, 0xBF,
+ 0xE0, 0xB0, 0x78, 0x4C, 0x2E, 0x9B, 0xCF, 0xE8, 0xB4, 0x7A, 0xCD, 0x6E,
+ 0xBB, 0xDF, 0xF0, 0xB8, 0x7C, 0x4E, 0xAF, 0xDB, 0xEF, 0xF8, 0xBC, 0x7E,
+ 0xCF, 0xEF, 0xFB, 0xFF, 0x1B, 0xC5, 0x86, 0xB2, 0x41, 0x6D, 0x52, 0x9B,
+ 0xD7, 0x86, 0xB7, 0xE9, 0x6D, 0x7C, 0x1B, 0xE0, 0xA6, 0x0C
+ };
+ static const guint8 ext[] = {
+ 0x1B, 0x0A, 0x1B, 0x14, 0x1B, 0x28, 0x1B, 0x29, 0x1B, 0x2F, 0x1B, 0x3C,
+ 0x1B, 0x3D, 0x1B, 0x3E, 0x1B, 0x40, 0x1B, 0x65
+ };
+ guint8 *packed, c;
+ guint32 packed_len = 0;
+ GByteArray *unpacked;
+
+ unpacked = g_byte_array_sized_new (148);
+ for (c = 0; c < 128; c++)
+ g_byte_array_append (unpacked, &c, 1);
+ for (c = 0; c < sizeof (ext); c++)
+ g_byte_array_append (unpacked, &ext[c], 1);
+
+ packed = gsm_pack (unpacked->data, unpacked->len, 0, &packed_len);
+ g_assert (packed);
+ g_assert_cmpint (packed_len, ==, sizeof (expected));
+ g_assert_cmpint (memcmp (packed, expected, packed_len), ==, 0);
+
+ g_free (packed);
+ g_byte_array_free (unpacked, TRUE);
+}
+
+static void
+test_pack_gsm7_24_chars (void *f, gpointer d)
+{
+ static const guint8 unpacked[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
+ 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17
+ };
+ guint8 *packed;
+ guint32 packed_len = 0;
+
+ /* Tests that no empty trailing byte is added when all the 7-bit characters
+ * are packed into an exact number of bytes.
+ */
+
+ packed = gsm_pack (unpacked, sizeof (unpacked), 0, &packed_len);
+ g_assert (packed);
+ g_assert_cmpint (packed_len, ==, 21);
+
+ g_free (packed);
+}
+
+
+#if GLIB_CHECK_VERSION(2,25,12)
+typedef GTestFixtureFunc TCFunc;
+#else
+typedef void (*TCFunc)(void);
+#endif
+
+#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (TCFunc) t, NULL)
+
+int main (int argc, char **argv)
+{
+ GTestSuite *suite;
+ gint result;
+
+ g_test_init (&argc, &argv, NULL);
+
+ suite = g_test_get_root ();
+
+ g_test_suite_add (suite, TESTCASE (test_def_chars, NULL));
+ g_test_suite_add (suite, TESTCASE (test_esc_chars, NULL));
+ g_test_suite_add (suite, TESTCASE (test_mixed_chars, NULL));
+
+ g_test_suite_add (suite, TESTCASE (test_unpack_gsm7, NULL));
+ g_test_suite_add (suite, TESTCASE (test_unpack_gsm7_7_chars, NULL));
+ g_test_suite_add (suite, TESTCASE (test_unpack_gsm7_all_chars, NULL));
+
+ g_test_suite_add (suite, TESTCASE (test_pack_gsm7, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pack_gsm7_7_chars, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pack_gsm7_all_chars, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pack_gsm7_24_chars, NULL));
+
+ result = g_test_run ();
+
+ return result;
+}
+
diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c
index 92a7af8..946916f 100644
--- a/src/tests/test-modem-helpers.c
+++ b/src/tests/test-modem-helpers.c
@@ -17,6 +17,7 @@
#include <string.h>
#include "mm-modem-helpers.h"
+#include "mm-log.h"
typedef struct {
GPtrArray *solicited_creg;
@@ -711,6 +712,27 @@ test_creg_cgreg_multi2_unsolicited (void *f, gpointer d)
}
static void
+test_cgreg2_x220_unsolicited (void *f, gpointer d)
+{
+ TestData *data = (TestData *) d;
+ const char *reply = "\r\n+CGREG: 2,1, 81ED, 1A9CEB\r\n";
+ const CregResult result = { 1, 0x81ED, 0x1A9CEB, -1, 4, TRUE};
+
+ /* Tests random spaces in response */
+ test_creg_match ("Alcatel One-Touch X220D CGREG=2", FALSE, reply, data, &result);
+}
+
+static void
+test_creg2_s8500_wave_unsolicited (void *f, gpointer d)
+{
+ TestData *data = (TestData *) d;
+ const char *reply = "\r\n+CREG: 2,1,000B,2816, B, C2816\r\n";
+ const CregResult result = { 1, 0x000B, 0x2816, 0, 7, FALSE};
+
+ test_creg_match ("Samsung Wave S8500 CREG=2", FALSE, reply, data, &result);
+}
+
+static void
test_cscs_icon225_support_response (void *f, gpointer d)
{
const char *reply = "\r\n+CSCS: (\"IRA\",\"GSM\",\"UCS2\")\r\n";
@@ -771,6 +793,372 @@ test_cscs_blackberry_support_response (void *f, gpointer d)
g_assert (charsets == MM_MODEM_CHARSET_IRA);
}
+typedef struct {
+ char *devid;
+ char *desc;
+ guint vid;
+ guint pid;
+ const char *ati;
+ const char *ati1;
+ const char *gsn;
+ const char *revision;
+ const char *model;
+ const char *manf;
+} DevidItem;
+
+static DevidItem devids[] = {
+ { "36e7a8e78637fd380b2664507ea5de8fc317d05b",
+ "Huawei E1550",
+ 0x12d1, 0x1001,
+ "\nManufacturer: huawei\n"
+ "Model: E1550\n"
+ "Revision: 11.608.09.01.21\n"
+ "IMEI: 235012412595195\n"
+ "+GCAP: +CGSM,+FCLASS,+DS\n",
+ NULL,
+ "\n235012412595195\n",
+ "\n11.608.09.01.21\n",
+ "\nE1550\n",
+ "\nhuawei\n"
+ },
+ { "33b0fc4a06af5448df656ce12925979acf1cb600",
+ "Huawei EC121",
+ 0x12d1, 0x1411,
+ "\nManufacturer: HUAWEI INCORPORATED\n"
+ "Model: EC121\n"
+ "Revision: 11.100.17.00.114\n"
+ "ESN: +GSN:12de4fa6\n"
+ "+CIS707-A, +MS, +ES, +DS, +FCLASS\n",
+ NULL,
+ "\n12de4fa6\n",
+ "\n11.100.17.00.114\n",
+ "\nEC121\n",
+ "\nHUAWEI INCORPORATED\n"
+ },
+ { "d17f016a402354eaa1e24855f4308fafca9cadb1",
+ "Sierra USBConnect Mercury",
+ 0x1199, 0x6880,
+ "\nManufacturer: Sierra Wireless, Inc.\n"
+ "Model: C885\n"
+ "Revision: J1_0_1_26AP C:/WS/FW/J1_0_1_26AP/MSM7200A/SRC/AMSS 2009/01/30 07:58:06\n"
+ "IMEI: 987866969112306\n"
+ "IMEI SV: 6\n"
+ "FSN: D603478104511\n"
+ "3GPP Release 6\n"
+ "+GCAP: +CGSM,+DS,+ES\n",
+ NULL,
+ "\n987866969112306\n",
+ "\nJ1_0_1_26AP C:/WS/FW/J1_0_1_26AP/MSM7200A/SRC/AMSS 2009/01/30 07:58:06\n",
+ "\nC885\n",
+ "\nSierra Wireless, Inc.\n"
+ },
+ { "345e9eaad7624393aca85cde9bd859edf462414c",
+ "ZTE MF627",
+ 0x19d2, 0x0031,
+ "\nManufacturer: ZTE INCORPORATED\n"
+ "Model: MF627\n"
+ "Revision: BD_3GHAP673A4V1.0.0B02\n"
+ "IMEI: 023589923858188\n"
+ "+GCAP: +CGSM,+FCLASS,+DS\n",
+ NULL,
+ "\n023589923858188\n",
+ "\nBD_3GHAP673A4V1.0.0B02\n",
+ "\nMF627\n",
+ "\nZTE INCORPORATED\n"
+ },
+ { "69fa133a668b6f4dbf39b73500fd153ec240c73f",
+ "Sony-Ericsson MD300",
+ 0x0fce, 0xd0cf,
+ "\nMD300\n",
+ "\nR3A018\n",
+ "\n349583712939483\n",
+ "\nR3A018\n",
+ "\nMD300\n",
+ "\nSony Ericsson\n"
+ },
+ { "3dad89ed7d774938c38188cf29cf1c211e9d360b",
+ "Option iCON 7.2",
+ 0x0af0, 0x6901,
+ "\nManufacturer: Option N.V.\n"
+ "Model: GTM378\n"
+ "Revision: 2.5.21Hd (Date: Jun 17 2008, Time: 12:30:47)\n",
+ NULL,
+ "\n129512359199159,SE393939TS\n",
+ "\n2.5.21Hd (Date: Jun 17 2008, Time: 12:30:47)\n",
+ "\nGTM378\n",
+ "\nOption N.V.\n"
+ },
+ { "b0acccb956c9eaf2076e03697e74bf998dc44179",
+ "ZTE MF622",
+ 0x19d2, 0x0001,
+ NULL,
+ NULL,
+ "\n235251122555115\n",
+ "\n3UKP671M3V1.0.0B08 3UKP671M3V1.0.0B08 1 [Jan 07 2008 16:00:00]\n",
+ "\nMF622\n",
+ "\nZTE INCORPORATED\n"
+ },
+ { "29a5b258f1dc6f50c66a1a9a1ecdde97560799ab",
+ "Option 452",
+ 0x0af0, 0x7901,
+ "\nManufacturer: Option N.V.\n"
+ "Model: GlobeTrotter HSUPA Modem\n"
+ "Revision: 2.12.0.0Hd (Date: Oct 29 2009, Time: 09:56:48)\n",
+ "\nManufacturer: Option N.V.\n"
+ "Model: GlobeTrotter HSUPA Modem\n"
+ "Revision: 2.12.0.0Hd (Date: Oct 29 2009, Time: 09:56:48)\n",
+ "\n000125491259519,PH2155R3TR\n",
+ "\n2.12.0.0Hd (Date: Oct 29 2009, Time: 09:56:48)\n",
+ "\nGlobeTrotter HSUPA Modem\n",
+ "\nOption N.V.\n"
+ },
+ { "c756c67e960e693d5d221e381ea170b60bb9288f",
+ "Novatel XU870",
+ 0x413c, 0x8118,
+ "\nManufacturer: Novatel Wireless Incorporated\n"
+ "Model: DELL XU870 ExpressCard\n"
+ "Revision: 9.5.05.01-02 [2006-10-20 17:19:09]\n"
+ "IMEI: 012051505051501\n"
+ "+GCAP: +CGSM,+DS\n",
+ "\nManufacturer: Novatel Wireless Incorporated\n"
+ "Model: DELL XU870 ExpressCard\n"
+ "Revision: 9.5.05.01-02 [2006-10-20 17:19:09]\n"
+ "IMEI: 012051505051501\n"
+ "+GCAP: +CGSM,+DS\n",
+ "\n012051505051501\n",
+ "\n9.5.05.01-02 [2006-10-20 17:19:09]\n",
+ "\nDELL XU870 ExpressCard\n",
+ "\nNovatel Wireless Incorporated\n"
+ },
+ { "4162ba918ab54b7776bccc3830e6c6b7a6738244",
+ "Zoom 4596",
+ 0x1c9e, 0x9603,
+ "\nManufacturer: Manufacturer\n"
+ "Model: HSPA USB MODEM\n"
+ "Revision: LQA0021.1.1_M573A\n"
+ "IMEI: 239664699635121\n"
+ "+GCAP: +CGSM,+FCLASS,+DS\n",
+ "\nManufacturer: Manufacturer\n"
+ "Model: HSPA USB MODEM\n"
+ "Revision: LQA0021.1.1_M573A\n"
+ "IMEI: 239664699635121\n"
+ "+GCAP: +CGSM,+FCLASS,+DS\n",
+ "\n239664699635121\n",
+ "\nLQA0021.1.1_M573A\n",
+ "\nHSPA USB MODEM\n",
+ "\nManufacturer\n"
+ },
+ { "6d3a2fccd3588943a8962fd1e0d3ba752c706660",
+ "C-MOTECH CDX-650",
+ 0x16d8, 0x6512,
+ "\nManufacturer: C-MOTECH Co., Ltd.\r\r\n"
+ "Model: CDX-650 \r\r\n"
+ "Revision: CDX65UAC03\r\r\n"
+ "Esn: 3B0C4B98\r\r\n"
+ "+GCAP: +CIS707A, +MS, +ES, +DS, +FCLASS\r\n",
+ "\nManufacturer: C-MOTECH Co., Ltd.\r\r\n"
+ "Model: CDX-650 \r\r\n"
+ "Revision: CDX65UAC03\r\r\n"
+ "Esn: 3B0C4B98\r\r\n"
+ "+GCAP: +CIS707A, +MS, +ES, +DS, +FCLASS\r\n",
+ "\n0x3B0C4B98\n",
+ "\nCDX65UAC03 1 [Oct 17 2007 13:30:00]\n",
+ "\nModel CDX-650 \n",
+ "\nC-MOTECH Co., Ltd.\n"
+ },
+ { "cf50da63e6d48beb1d1c3b41d70ef6fa534c3e13",
+ "BUSlink SCWi275u",
+ 0x22b8, 0x3802,
+ "\n144\n",
+ "\n000\n",
+ NULL,
+ "\n\"ADE_05_00_06032300I\"\n",
+ "\n\"GSM900\",\"GSM1800\",\"GSM1900\",\"GSM850\",\"MODEL=I250-000\"\n",
+ "\n\"Motorola CE, Copyright 2000\"\n"
+ },
+ { "2aff568f2b60f3d6f3f6cac708ed5dce77b12b96",
+ "Motorola ROKR E2",
+ 0x22b8, 0x3802,
+ NULL,
+ NULL,
+ "\n\"626936926396996\"\n",
+ "\n\"R564_G_12.00.47P\"\n",
+ "\n\"E2\"\n",
+ "\n\"Motorola\"\n"
+ },
+ { "a7136c6067a43f055ca093cee75cb98ce6c9658e",
+ "Sony-Ericsson W580i",
+ 0x0fce, 0xd089,
+ "\nSony Ericsson W580\n",
+ "\nCXC1123481\n",
+ "\n012505051512505\n",
+ "\nR8BE001 080115 1451 CXC1123481_NAM_1_LA\n",
+ "\nAAC-1052042-BV\n",
+ "\nSony Ericsson\n"
+ },
+ { "b80ee70214bdf9672f2a268ce165ecfd9def5721",
+ "Huawei E226",
+ 0x12d1, 0x1003,
+ "\nManufacturer: huawei\n"
+ "Model: E226\n"
+ "Revision: 11.310.15.00.150\n"
+ "IMEI: 232363662362362\n"
+ "+GCAP: +CGSM,+FCLASS,+DS\n",
+ "\nManufacturer: huawei\n"
+ "Model: E226\n"
+ "Revision: 11.310.15.00.150\n"
+ "IMEI: 232363662362362\n"
+ "+GCAP: +CGSM,+FCLASS,+DS\n",
+ "\n232363662362362\n",
+ "\n11.310.15.00.150\n",
+ "\nE226\n",
+ "\nhuawei\n"
+ },
+ { "d902e1f234863aa107bfc2d0faefbee5ed6901f1",
+ "LG LX265",
+ 0x1004, 0x6000,
+ "\nManufacturer: +GMI: LG Electronics Inc.\n"
+ "Model: +GMI: LG Electronics Inc.+GMM: Model:LG-LX265\n"
+ "Revision: +GMR: LX265V05, 50571\n"
+ "ESN: +GSN: 0x9235EB52\n"
+ "+GCAP: +CIS707-A, +MS, +ES, +DS, +FCLASS\n",
+ "\nManufacturer: +GMI: LG Electronics Inc.\n"
+ "Model: +GMI: LG Electronics Inc.+GMM: Model:LG-LX265\n"
+ "Revision: +GMR: LX265V05, 50571\n"
+ "ESN: +GSN: 0x9235EB52\n"
+ "+GCAP: +CIS707-A, +MS, +ES, +DS, +FCLASS\n",
+ "\n0x9235EB52\n",
+ "\nLX265V05, 50571\n",
+ "\nModel:LG-LX265\n",
+ "\nLG Electronics Inc.\n"
+ },
+ { "543c2920e450e20a46368861fdec3a3b97ba8663",
+ "Nokia 2720a BT",
+ 0x0000, 0x0000,
+ "\nNokia\n",
+ "\n012350150101501\n",
+ "\n012350150101501\n",
+ "\nV 08.62\n"
+ "24-07-09\n"
+ "RM-520\n"
+ "(c) Nokia \n",
+ "\nNokia 2720a-2b\n",
+ "\nNokia\n"
+ },
+ { "6386ffa7a39ced3c9bfd1d693b90975661e54a86",
+ "Gobi 1000",
+ 0x03f0, 0x1f1d,
+ "\nManufacturer: QUALCOMM INCORPORATED\n"
+ "Model: 88\n"
+ "Revision: D1020-SUUAASFA-4352 1 [Apr 14 2008 18:00:00]\n"
+ "IMEI: 239639269236269\n"
+ "+GCAP: +CGSM,+DS\n",
+ "\nManufacturer: QUALCOMM INCORPORATED\n"
+ "Model: 88\n"
+ "Revision: D1020-SUUAASFA-4352 1 [Apr 14 2008 18:00:00]\n"
+ "IMEI: 239639269236269\n"
+ "+GCAP: +CGSM,+DS\n",
+ "\n239639269236269\n",
+ "\nD1020-SUUAASFA-4352 1 [Apr 14 2008 18:00:00]\n",
+ "\n88\n",
+ "\nQUALCOMM INCORPORATED\n"
+ },
+ { NULL }
+};
+
+static void
+test_devid_item (void *f, gpointer d)
+{
+ DevidItem *item = (DevidItem *) d;
+ char *devid;
+
+ g_print ("%s... ", item->desc);
+ devid = mm_create_device_identifier (item->vid,
+ item->pid,
+ item->ati,
+ item->ati1,
+ item->gsn,
+ item->revision,
+ item->model,
+ item->manf);
+ g_assert (devid);
+ if (strcmp (devid, item->devid))
+ g_message ("%s", devid);
+ g_assert (!strcmp (devid, item->devid));
+}
+
+typedef struct {
+ const char *desc;
+ const gint min;
+ const gint max;
+} CindEntry;
+
+static void
+test_cind_results (const char *desc,
+ const char *reply,
+ CindEntry *expected_results,
+ guint32 expected_results_len)
+{
+ guint i;
+ GError *error = NULL;
+ GHashTable *results;
+
+ g_print ("\nTesting %s +CIND response...\n", desc);
+
+ results = mm_parse_cind_test_response (reply, &error);
+ g_assert (results);
+ g_assert (error == NULL);
+
+ g_assert (g_hash_table_size (results) == expected_results_len);
+
+ for (i = 0; i < expected_results_len; i++) {
+ CindEntry *expected = &expected_results[i];
+ CindResponse *compare;
+
+ compare = g_hash_table_lookup (results, expected->desc);
+ g_assert (compare);
+ g_assert_cmpint (i + 1, ==, cind_response_get_index (compare));
+ g_assert_cmpint (expected->min, ==, cind_response_get_min (compare));
+ g_assert_cmpint (expected->max, ==, cind_response_get_max (compare));
+ }
+
+ g_hash_table_destroy (results);
+}
+
+static void
+test_cind_response_linktop_lw273 (void *f, gpointer d)
+{
+ const char *reply = "+CIND: (\"battchg\",(0-5)),(\"signal\",(0-5)),(\"batterywarning\",(0-1)),(\"chargerconnected\",(0-1)),(\"service\",(0-1)),(\"sounder\",(0-1)),(\"message\",(0-1)),()";
+ static CindEntry expected[] = {
+ { "battchg", 0, 5 },
+ { "signal", 0, 5 },
+ { "batterywarning", 0, 1 },
+ { "chargerconnected", 0, 1 },
+ { "service", 0, 1 },
+ { "sounder", 0, 1 },
+ { "message", 0, 1 }
+ };
+
+ test_cind_results ("LW273", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cind_response_moto_v3m (void *f, gpointer d)
+{
+ const char *reply = "+CIND: (\"Voice Mail\",(0,1)),(\"service\",(0,1)),(\"call\",(0,1)),(\"Roam\",(0-2)),(\"signal\",(0-5)),(\"callsetup\",(0-3)),(\"smsfull\",(0,1))";
+ static CindEntry expected[] = {
+ { "voicemail", 0, 1 },
+ { "service", 0, 1 },
+ { "call", 0, 1 },
+ { "roam", 0, 2 },
+ { "signal", 0, 5 },
+ { "callsetup", 0, 3 },
+ { "smsfull", 0, 1 }
+ };
+
+ test_cind_results ("Motorola V3m", reply, &expected[0], ARRAY_LEN (expected));
+}
+
static TestData *
test_data_new (void)
{
@@ -790,8 +1178,21 @@ test_data_free (TestData *data)
g_free (data);
}
+void
+_mm_log (const char *loc,
+ const char *func,
+ guint32 level,
+ const char *fmt,
+ ...)
+{
+ /* Dummy log function */
+}
+#if GLIB_CHECK_VERSION(2,25,12)
+typedef GTestFixtureFunc TCFunc;
+#else
typedef void (*TCFunc)(void);
+#endif
#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (TCFunc) t, NULL)
@@ -800,6 +1201,7 @@ int main (int argc, char **argv)
GTestSuite *suite;
TestData *data;
gint result;
+ DevidItem *item = &devids[0];
g_test_init (&argc, &argv, NULL);
@@ -848,12 +1250,14 @@ int main (int argc, char **argv)
g_test_suite_add (suite, TESTCASE (test_creg2_tm506_solicited, data));
g_test_suite_add (suite, TESTCASE (test_creg2_xu870_unsolicited_unregistered, data));
g_test_suite_add (suite, TESTCASE (test_creg2_md400_unsolicited, data));
+ g_test_suite_add (suite, TESTCASE (test_creg2_s8500_wave_unsolicited, data));
g_test_suite_add (suite, TESTCASE (test_cgreg1_solicited, data));
g_test_suite_add (suite, TESTCASE (test_cgreg1_unsolicited, data));
g_test_suite_add (suite, TESTCASE (test_cgreg2_f3607gw_solicited, data));
g_test_suite_add (suite, TESTCASE (test_cgreg2_f3607gw_unsolicited, data));
g_test_suite_add (suite, TESTCASE (test_cgreg2_md400_unsolicited, data));
+ g_test_suite_add (suite, TESTCASE (test_cgreg2_x220_unsolicited, data));
g_test_suite_add (suite, TESTCASE (test_creg_cgreg_multi_unsolicited, data));
g_test_suite_add (suite, TESTCASE (test_creg_cgreg_multi2_unsolicited, data));
@@ -863,6 +1267,14 @@ int main (int argc, char **argv)
g_test_suite_add (suite, TESTCASE (test_cscs_buslink_support_response, data));
g_test_suite_add (suite, TESTCASE (test_cscs_blackberry_support_response, data));
+ g_test_suite_add (suite, TESTCASE (test_cind_response_linktop_lw273, data));
+ g_test_suite_add (suite, TESTCASE (test_cind_response_moto_v3m, data));
+
+ while (item->devid) {
+ g_test_suite_add (suite, TESTCASE (test_devid_item, (gconstpointer) item));
+ item++;
+ }
+
result = g_test_run ();
test_data_free (data);
diff --git a/src/tests/test-qcdm-serial-port.c b/src/tests/test-qcdm-serial-port.c
new file mode 100644
index 0000000..3aeed6a
--- /dev/null
+++ b/src/tests/test-qcdm-serial-port.c
@@ -0,0 +1,482 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <string.h>
+#include <pty.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+#include "mm-errors.h"
+#include "mm-qcdm-serial-port.h"
+#include "libqcdm/src/commands.h"
+#include "libqcdm/src/utils.h"
+#include "libqcdm/src/com.h"
+#include "mm-log.h"
+
+typedef struct {
+ int master;
+ int slave;
+ gboolean valid;
+ pid_t child;
+} TestData;
+
+static gboolean
+wait_for_child (TestData *d, guint32 timeout)
+{
+ GTimeVal start, now;
+ int status, ret;
+
+ g_get_current_time (&start);
+ do {
+ status = 0;
+ ret = waitpid (d->child, &status, WNOHANG);
+ g_get_current_time (&now);
+ if (d->child && (now.tv_sec - start.tv_sec > timeout)) {
+ /* Kill it */
+ if (g_test_verbose ())
+ g_message ("Killing running child process %d", d->child);
+ kill (d->child, SIGKILL);
+ d->child = 0;
+ }
+ if (ret == 0)
+ sleep (1);
+ } while ((ret <= 0) || (!WIFEXITED (status) && !WIFSIGNALED (status)));
+
+ d->child = 0;
+ return (WIFEXITED (status) && WEXITSTATUS (status) == 0) ? TRUE : FALSE;
+}
+
+static void
+print_buf (const char *detail, const char *buf, gsize len)
+{
+ int i = 0;
+ gboolean newline = FALSE;
+
+ g_print ("%s (%zu) ", detail, len);
+ for (i = 0; i < len; i++) {
+ g_print ("0x%02x ", buf[i] & 0xFF);
+ if (((i + 1) % 12) == 0) {
+ g_print ("\n");
+ newline = TRUE;
+ } else
+ newline = FALSE;
+ }
+
+ if (!newline)
+ g_print ("\n");
+}
+
+static void
+server_send_response (int fd, const char *buf, gsize len)
+{
+ int status;
+ gsize i = 0;
+
+ if (g_test_verbose ())
+ print_buf (">>>", buf, len);
+
+ while (i < len) {
+ errno = 0;
+ status = write (fd, &buf[i], 1);
+ g_assert_cmpint (errno, ==, 0);
+ g_assert (status == 1);
+ i++;
+ usleep (1000);
+ }
+}
+
+static gsize
+server_wait_request (int fd, char *buf, gsize len)
+{
+ fd_set in;
+ int result;
+ struct timeval timeout = { 1, 0 };
+ char readbuf[1024];
+ ssize_t bytes_read;
+ int total = 0, retries = 0;
+ gsize decap_len = 0;
+
+ FD_ZERO (&in);
+ FD_SET (fd, &in);
+ result = select (fd + 1, &in, NULL, NULL, &timeout);
+ g_assert (result == 1);
+ g_assert (FD_ISSET (fd, &in));
+
+ do {
+ errno = 0;
+ bytes_read = read (fd, &readbuf[total], 1);
+ if ((bytes_read == 0) || (errno == EAGAIN)) {
+ /* Haven't gotten the async control char yet */
+ if (retries > 20)
+ return 0; /* 2 seconds, give up */
+
+ /* Otherwise wait a bit and try again */
+ usleep (100000);
+ retries++;
+ continue;
+ } else if (bytes_read == 1) {
+ gboolean more = FALSE, success;
+ gsize used = 0;
+
+ total++;
+ decap_len = 0;
+ success = dm_decapsulate_buffer (readbuf, total, buf, len, &decap_len, &used, &more);
+
+ /* Discard used data */
+ if (used > 0) {
+ total -= used;
+ memmove (readbuf, &readbuf[used], total);
+ }
+
+ if (success && !more) {
+ /* Success; we have a packet */
+ break;
+ }
+ } else {
+ /* Some error occurred */
+ g_assert_not_reached ();
+ }
+ } while (total < sizeof (readbuf));
+
+ if (g_test_verbose ()) {
+ print_buf ("<<<", readbuf, total);
+ print_buf ("D<<", buf, decap_len);
+ }
+
+ return decap_len;
+}
+
+typedef void (*VerInfoCb) (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ gpointer user_data);
+
+static void
+qcdm_verinfo_expect_success_cb (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ gpointer user_data)
+{
+ GMainLoop *loop = user_data;
+
+ g_assert_no_error (error);
+ g_assert (response->len > 0);
+ g_main_loop_quit (loop);
+}
+
+static void
+qcdm_request_verinfo (MMQcdmSerialPort *port, VerInfoCb cb, GMainLoop *loop)
+{
+ GError *error = NULL;
+ GByteArray *verinfo;
+ gint len;
+
+ /* Build up the probe command */
+ verinfo = g_byte_array_sized_new (50);
+ len = qcdm_cmd_version_info_new ((char *) verinfo->data, 50, &error);
+ if (len <= 0) {
+ g_byte_array_free (verinfo, TRUE);
+ g_assert_no_error (error);
+ }
+ verinfo->len = len;
+
+ mm_qcdm_serial_port_queue_command (port, verinfo, 3, cb, loop);
+}
+
+static void
+qcdm_test_child (int fd, VerInfoCb cb)
+{
+ MMQcdmSerialPort *port;
+ GMainLoop *loop;
+ gboolean success;
+ GError *error = NULL;
+
+ /* In the child */
+ g_type_init ();
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ port = mm_qcdm_serial_port_new_fd (fd, MM_PORT_TYPE_PRIMARY);
+ g_assert (port);
+
+ success = mm_serial_port_open (MM_SERIAL_PORT (port), &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ qcdm_request_verinfo (port, cb, loop);
+ g_main_loop_run (loop);
+
+ mm_serial_port_close (MM_SERIAL_PORT (port));
+ g_object_unref (port);
+}
+
+/* Test that a Version Info request/response is processed correctly to
+ * make sure things in general are working.
+ */
+static void
+test_verinfo (void *f)
+{
+ TestData *d = f;
+ char req[512];
+ gsize req_len;
+ pid_t cpid;
+ const char rsp[] = {
+ 0x00, 0x41, 0x75, 0x67, 0x20, 0x31, 0x39, 0x20, 0x32, 0x30, 0x30, 0x38,
+ 0x32, 0x30, 0x3a, 0x34, 0x38, 0x3a, 0x34, 0x37, 0x4f, 0x63, 0x74, 0x20,
+ 0x32, 0x39, 0x20, 0x32, 0x30, 0x30, 0x37, 0x31, 0x39, 0x3a, 0x30, 0x30,
+ 0x3a, 0x30, 0x30, 0x53, 0x43, 0x4e, 0x52, 0x5a, 0x2e, 0x2e, 0x2e, 0x2a,
+ 0x06, 0x04, 0xb9, 0x0b, 0x02, 0x00, 0xb2, 0x19, 0xc4, 0x7e
+ };
+
+ signal (SIGCHLD, SIG_DFL);
+ cpid = fork ();
+ g_assert (cpid >= 0);
+
+ if (cpid == 0) {
+ /* In the child */
+ qcdm_test_child (d->slave, qcdm_verinfo_expect_success_cb);
+ exit (0);
+ }
+ /* Parent */
+ d->child = cpid;
+
+ req_len = server_wait_request (d->master, req, sizeof (req));
+ g_assert (req_len == 1);
+ g_assert_cmpint (req[0], ==, 0x00);
+
+ server_send_response (d->master, rsp, sizeof (rsp));
+ g_assert (wait_for_child (d, 3));
+}
+
+static void
+qcdm_verinfo_expect_fail_cb (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ gpointer user_data)
+{
+ GMainLoop *loop = user_data;
+
+ g_assert_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL);
+ g_main_loop_quit (loop);
+}
+
+/* Test that a Sierra CnS response to a Version Info command correctly
+ * raises an error in the child's response handler.
+ */
+static void
+test_sierra_cns_rejected (void *f)
+{
+ TestData *d = f;
+ char req[512];
+ gsize req_len;
+ pid_t cpid;
+ const char rsp[] = {
+ 0x7e, 0x00, 0x0a, 0x6b, 0x6d, 0x00, 0x00, 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e
+ };
+
+ signal (SIGCHLD, SIG_DFL);
+ cpid = fork ();
+ g_assert (cpid >= 0);
+
+ if (cpid == 0) {
+ /* In the child */
+ qcdm_test_child (d->slave, qcdm_verinfo_expect_fail_cb);
+ exit (0);
+ }
+ /* Parent */
+ d->child = cpid;
+
+ req_len = server_wait_request (d->master, req, sizeof (req));
+ g_assert (req_len == 1);
+ g_assert_cmpint (req[0], ==, 0x00);
+
+ server_send_response (d->master, rsp, sizeof (rsp));
+
+ /* We expect the child to exit normally */
+ g_assert (wait_for_child (d, 3));
+}
+
+/* Test that a random response to a Version Info command correctly
+ * raises an error in the child's response handler.
+ */
+static void
+test_random_data_rejected (void *f)
+{
+ TestData *d = f;
+ char req[512];
+ gsize req_len;
+ pid_t cpid;
+ const char rsp[] = {
+ 0x7e, 0x7e, 0x7e, 0x6b, 0x6d, 0x7e, 0x7e, 0x7e, 0x7e,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e
+ };
+
+ signal (SIGCHLD, SIG_DFL);
+ cpid = fork ();
+ g_assert (cpid >= 0);
+
+ if (cpid == 0) {
+ /* In the child */
+ qcdm_test_child (d->slave, qcdm_verinfo_expect_fail_cb);
+ exit (0);
+ }
+ /* Parent */
+ d->child = cpid;
+
+ req_len = server_wait_request (d->master, req, sizeof (req));
+ g_assert (req_len == 1);
+ g_assert_cmpint (req[0], ==, 0x00);
+
+ server_send_response (d->master, rsp, sizeof (rsp));
+
+ /* We expect the child to exit normally */
+ g_assert (wait_for_child (d, 3));
+}
+
+/* Test that a bunch of frame markers at the beginning of a valid response
+ * to a Version Info command is parsed correctly.
+ */
+static void
+test_leading_frame_markers (void *f)
+{
+ TestData *d = f;
+ char req[512];
+ gsize req_len;
+ pid_t cpid;
+ const char rsp[] = {
+ 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e,
+ 0x00, 0x41, 0x75, 0x67, 0x20, 0x31, 0x39, 0x20, 0x32, 0x30, 0x30, 0x38,
+ 0x32, 0x30, 0x3a, 0x34, 0x38, 0x3a, 0x34, 0x37, 0x4f, 0x63, 0x74, 0x20,
+ 0x32, 0x39, 0x20, 0x32, 0x30, 0x30, 0x37, 0x31, 0x39, 0x3a, 0x30, 0x30,
+ 0x3a, 0x30, 0x30, 0x53, 0x43, 0x4e, 0x52, 0x5a, 0x2e, 0x2e, 0x2e, 0x2a,
+ 0x06, 0x04, 0xb9, 0x0b, 0x02, 0x00, 0xb2, 0x19, 0xc4, 0x7e
+ };
+
+ signal (SIGCHLD, SIG_DFL);
+ cpid = fork ();
+ g_assert (cpid >= 0);
+
+ if (cpid == 0) {
+ /* In the child */
+ qcdm_test_child (d->slave, qcdm_verinfo_expect_success_cb);
+ exit (0);
+ }
+ /* Parent */
+ d->child = cpid;
+
+ req_len = server_wait_request (d->master, req, sizeof (req));
+ g_assert (req_len == 1);
+ g_assert_cmpint (req[0], ==, 0x00);
+
+ server_send_response (d->master, rsp, sizeof (rsp));
+
+ /* We expect the child to exit normally */
+ g_assert (wait_for_child (d, 3));
+}
+
+static void
+test_pty_create (gpointer user_data)
+{
+ TestData *d = user_data;
+ struct termios stbuf;
+ int ret;
+ GError *error = NULL;
+ gboolean success;
+
+ ret = openpty (&d->master, &d->slave, NULL, NULL, NULL);
+ g_assert (ret == 0);
+ d->valid = TRUE;
+
+ /* set raw mode on the slave using kernel default parameters */
+ memset (&stbuf, 0, sizeof (stbuf));
+ tcgetattr (d->slave, &stbuf);
+ tcflush (d->slave, TCIOFLUSH);
+ cfmakeraw (&stbuf);
+ tcsetattr (d->slave, TCSANOW, &stbuf);
+ fcntl (d->slave, F_SETFL, O_NONBLOCK);
+
+ fcntl (d->master, F_SETFL, O_NONBLOCK);
+ success = qcdm_port_setup (d->master, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+}
+
+static void
+test_pty_cleanup (gpointer user_data)
+{
+ TestData *d = user_data;
+
+ /* For some reason the cleanup function gets called more times
+ * than the setup function does...
+ */
+ if (d->valid) {
+ if (d->child)
+ kill (d->child, SIGKILL);
+ if (d->master >= 0)
+ close (d->master);
+ if (d->slave >= 0)
+ close (d->slave);
+ memset (d, 0, sizeof (*d));
+ }
+}
+
+#if GLIB_CHECK_VERSION(2,25,12)
+typedef GTestFixtureFunc TCFunc;
+#else
+typedef void (*TCFunc)(void);
+#endif
+
+#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (TCFunc) t, NULL)
+#define TESTCASE_PTY(t, d) g_test_create_case (#t, sizeof (*d), d, (TCFunc) test_pty_create, (TCFunc) t, (TCFunc) test_pty_cleanup)
+
+void
+_mm_log (const char *loc,
+ const char *func,
+ guint32 level,
+ const char *fmt,
+ ...)
+{
+ /* Dummy log function */
+}
+
+int main (int argc, char **argv)
+{
+ GTestSuite *suite;
+ gint result;
+ TestData *data = NULL;
+
+ g_test_init (&argc, &argv, NULL);
+
+ suite = g_test_get_root ();
+
+ g_test_suite_add (suite, TESTCASE_PTY (test_verinfo, data));
+ g_test_suite_add (suite, TESTCASE_PTY (test_sierra_cns_rejected, data));
+ g_test_suite_add (suite, TESTCASE_PTY (test_random_data_rejected, data));
+ g_test_suite_add (suite, TESTCASE_PTY (test_leading_frame_markers, data));
+
+ result = g_test_run ();
+
+ return result;
+}
+