aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2014-02-05 08:38:15 +0100
committerGuido Günther <agx@sigxcpu.org>2014-02-05 08:38:15 +0100
commit87bd9deec22af69bb27226254803ac5c63b18d78 (patch)
treec34d42bf75c20b3fd740e4cd59e45aa6901a9fed /src
Imported Upstream version 0.3upstream/0.3
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am99
-rw-r--r--src/main.c211
-rw-r--r--src/mm-callback-info.c210
-rw-r--r--src/mm-callback-info.h74
-rw-r--r--src/mm-errors.c290
-rw-r--r--src/mm-errors.h131
-rw-r--r--src/mm-generic-cdma.c1831
-rw-r--r--src/mm-generic-cdma.h111
-rw-r--r--src/mm-generic-gsm.c2220
-rw-r--r--src/mm-generic-gsm.h129
-rw-r--r--src/mm-manager.c714
-rw-r--r--src/mm-manager.h53
-rw-r--r--src/mm-modem-base.c341
-rw-r--r--src/mm-modem-base.h64
-rw-r--r--src/mm-modem-cdma.c346
-rw-r--r--src/mm-modem-cdma.h108
-rw-r--r--src/mm-modem-gsm-card.c312
-rw-r--r--src/mm-modem-gsm-card.h101
-rw-r--r--src/mm-modem-gsm-network.c569
-rw-r--r--src/mm-modem-gsm-network.h164
-rw-r--r--src/mm-modem-gsm-sms.c320
-rw-r--r--src/mm-modem-gsm-sms.h62
-rw-r--r--src/mm-modem-gsm.h57
-rw-r--r--src/mm-modem-helpers.c203
-rw-r--r--src/mm-modem-helpers.h31
-rw-r--r--src/mm-modem-simple.c156
-rw-r--r--src/mm-modem-simple.h59
-rw-r--r--src/mm-modem.c738
-rw-r--r--src/mm-modem.h219
-rw-r--r--src/mm-options.c55
-rw-r--r--src/mm-options.h23
-rw-r--r--src/mm-plugin-base.c1126
-rw-r--r--src/mm-plugin-base.h150
-rw-r--r--src/mm-plugin.c100
-rw-r--r--src/mm-plugin.h120
-rw-r--r--src/mm-port.c278
-rw-r--r--src/mm-port.h79
-rw-r--r--src/mm-properties-changed-signal.c276
-rw-r--r--src/mm-properties-changed-signal.h28
-rw-r--r--src/mm-serial-parsers.c372
-rw-r--r--src/mm-serial-parsers.h44
-rw-r--r--src/mm-serial-port.c1281
-rw-r--r--src/mm-serial-port.h105
-rw-r--r--src/tests/Makefile.am22
-rw-r--r--src/tests/test-modem-helpers.c479
45 files changed, 14461 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..9209b55
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,99 @@
+SUBDIRS=. tests
+
+noinst_LTLIBRARIES = libmodem-helpers.la
+
+libmodem_helpers_la_CPPFLAGS = \
+ $(MM_CFLAGS)
+
+libmodem_helpers_la_SOURCES = \
+ mm-errors.c \
+ mm-errors.h \
+ mm-modem-helpers.c \
+ mm-modem-helpers.h
+
+sbin_PROGRAMS = modem-manager
+
+modem_manager_CPPFLAGS = \
+ $(MM_CFLAGS) \
+ $(GUDEV_CFLAGS) \
+ -I${top_builddir}/marshallers \
+ -DPLUGINDIR=\"$(pkglibdir)\"
+
+modem_manager_LDADD = \
+ $(MM_LIBS) \
+ $(GUDEV_LIBS) \
+ $(top_builddir)/marshallers/libmarshallers.la \
+ $(builddir)/libmodem-helpers.la
+
+modem_manager_SOURCES = \
+ main.c \
+ mm-callback-info.c \
+ mm-callback-info.h \
+ mm-manager.c \
+ mm-manager.h \
+ mm-modem.c \
+ mm-modem.h \
+ mm-port.c \
+ mm-port.h \
+ mm-modem-base.c \
+ mm-modem-base.h \
+ mm-serial-port.c \
+ mm-serial-port.h \
+ mm-serial-parsers.c \
+ mm-serial-parsers.h \
+ mm-generic-cdma.c \
+ mm-generic-cdma.h \
+ mm-generic-gsm.c \
+ mm-generic-gsm.h \
+ mm-modem-cdma.c \
+ mm-modem-cdma.h \
+ mm-modem-gsm.h \
+ mm-modem-gsm-card.c \
+ mm-modem-gsm-card.h \
+ mm-modem-gsm-network.c \
+ mm-modem-gsm-network.h \
+ mm-modem-gsm-sms.c \
+ mm-modem-gsm-sms.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-manager-glue.h: $(top_srcdir)/introspection/mm-manager.xml
+ 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-simple-glue.h: $(top_srcdir)/introspection/mm-modem-simple.xml
+ 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-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-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-sms-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-sms.xml
+ dbus-binding-tool --prefix=mm_modem_gsm_sms --mode=glib-server --output=$@ $<
+
+
+BUILT_SOURCES = \
+ mm-manager-glue.h \
+ mm-modem-glue.h \
+ mm-modem-simple-glue.h \
+ mm-modem-cdma-glue.h \
+ mm-modem-gsm-card-glue.h \
+ mm-modem-gsm-network-glue.h \
+ mm-modem-gsm-sms-glue.h
+
+CLEANFILES = $(BUILT_SOURCES)
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..3669115
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,211 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include <signal.h>
+#include <syslog.h>
+#include <string.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include "mm-manager.h"
+#include "mm-options.h"
+
+#define HAL_DBUS_SERVICE "org.freedesktop.Hal"
+
+static GMainLoop *loop = NULL;
+
+static void
+mm_signal_handler (int signo)
+{
+ if (signo == SIGUSR1)
+ mm_options_set_debug (!mm_options_debug ());
+ else if (signo == SIGINT || signo == SIGTERM) {
+ g_message ("Caught signal %d, shutting down...", signo);
+ g_main_loop_quit (loop);
+ }
+}
+
+static void
+setup_signals (void)
+{
+ struct sigaction action;
+ sigset_t mask;
+
+ sigemptyset (&mask);
+ action.sa_handler = mm_signal_handler;
+ action.sa_mask = mask;
+ action.sa_flags = 0;
+ sigaction (SIGUSR1, &action, NULL);
+ sigaction (SIGTERM, &action, NULL);
+ sigaction (SIGINT, &action, NULL);
+}
+
+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.");
+ g_main_loop_quit (loop);
+}
+
+static DBusGProxy *
+create_dbus_proxy (DBusGConnection *bus)
+{
+ DBusGProxy *proxy;
+ GError *err = NULL;
+ int request_name_result;
+
+ proxy = dbus_g_proxy_new_for_name (bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus");
+
+ if (!dbus_g_proxy_call (proxy, "RequestName", &err,
+ G_TYPE_STRING, MM_DBUS_SERVICE,
+ G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE,
+ 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);
+
+ 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);
+
+ g_object_unref (proxy);
+ proxy = NULL;
+ } else {
+ dbus_g_proxy_add_signal (proxy, "NameOwnerChanged",
+ G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_INVALID);
+ }
+
+ return proxy;
+}
+
+static gboolean
+start_manager (gpointer user_data)
+{
+ mm_manager_start (MM_MANAGER (user_data));
+ return FALSE;
+}
+
+int
+main (int argc, char *argv[])
+{
+ DBusGConnection *bus;
+ DBusGProxy *proxy;
+ MMManager *manager;
+ GError *err = NULL;
+ guint id;
+
+ mm_options_parse (argc, argv);
+ g_type_init ();
+
+ setup_signals ();
+
+ if (!mm_options_debug ())
+ logging_setup ();
+
+ bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err);
+ if (!bus) {
+ g_warning ("Could not get the system bus. Make sure "
+ "the message bus daemon is running! Message: %s",
+ err->message);
+ g_error_free (err);
+ return -1;
+ }
+
+ proxy = create_dbus_proxy (bus);
+ if (!proxy)
+ return -1;
+
+ manager = mm_manager_new (bus);
+ g_idle_add (start_manager, manager);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ id = g_signal_connect (proxy, "destroy", G_CALLBACK (destroy_cb), loop);
+
+ g_main_loop_run (loop);
+
+ g_signal_handler_disconnect (proxy, id);
+
+ g_object_unref (manager);
+ g_object_unref (proxy);
+ dbus_g_connection_unref (bus);
+
+ logging_shutdown ();
+
+ return 0;
+}
diff --git a/src/mm-callback-info.c b/src/mm-callback-info.c
new file mode 100644
index 0000000..1882898
--- /dev/null
+++ b/src/mm-callback-info.c
@@ -0,0 +1,210 @@
+/* -*- 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.
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ */
+
+#include "mm-callback-info.h"
+#include "mm-errors.h"
+
+#define CALLBACK_INFO_RESULT "callback-info-result"
+
+static void
+invoke_mm_modem_fn (MMCallbackInfo *info)
+{
+ MMModemFn callback = (MMModemFn) info->callback;
+
+ callback (info->modem, info->error, info->user_data);
+}
+
+static void
+invoke_mm_modem_uint_fn (MMCallbackInfo *info)
+{
+ MMModemUIntFn callback = (MMModemUIntFn) info->callback;
+
+ callback (info->modem,
+ GPOINTER_TO_UINT (mm_callback_info_get_data (info, CALLBACK_INFO_RESULT)),
+ info->error, info->user_data);
+}
+
+static void
+invoke_mm_modem_string_fn (MMCallbackInfo *info)
+{
+ MMModemStringFn callback = (MMModemStringFn) info->callback;
+
+ callback (info->modem,
+ (const char *) mm_callback_info_get_data (info, CALLBACK_INFO_RESULT),
+ info->error, info->user_data);
+}
+
+
+static void
+modem_destroyed_cb (gpointer data, GObject *destroyed)
+{
+ MMCallbackInfo *info = data;
+
+ info->modem = NULL;
+ if (!info->pending_id) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_REMOVED,
+ "The modem was removed.");
+ mm_callback_info_schedule (info);
+ }
+}
+
+static void
+callback_info_done (gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ info->pending_id = 0;
+ info->called = TRUE;
+
+ if (info->invoke_fn && info->callback)
+ info->invoke_fn (info);
+
+ mm_callback_info_unref (info);
+}
+
+static gboolean
+callback_info_do (gpointer user_data)
+{
+ /* Nothing here, everything is done in callback_info_done to make sure the info->callback
+ always gets called, even if the pending call gets cancelled. */
+ return FALSE;
+}
+
+void
+mm_callback_info_schedule (MMCallbackInfo *info)
+{
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (info->pending_id == 0);
+ g_return_if_fail (info->called == FALSE);
+
+ info->pending_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, callback_info_do, info, callback_info_done);
+}
+
+MMCallbackInfo *
+mm_callback_info_new_full (MMModem *modem,
+ MMCallbackInfoInvokeFn invoke_fn,
+ GCallback callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ g_return_val_if_fail (modem != NULL, NULL);
+
+ info = g_slice_new0 (MMCallbackInfo);
+ g_datalist_init (&info->qdata);
+ info->modem = modem;
+ g_object_weak_ref (G_OBJECT (modem), modem_destroyed_cb, info);
+ info->invoke_fn = invoke_fn;
+ info->callback = callback;
+ info->user_data = user_data;
+ info->refcount = 1;
+
+ return info;
+}
+
+MMCallbackInfo *
+mm_callback_info_new (MMModem *modem, MMModemFn callback, gpointer user_data)
+{
+ g_return_val_if_fail (modem != NULL, NULL);
+
+ return mm_callback_info_new_full (modem, invoke_mm_modem_fn, (GCallback) callback, user_data);
+}
+
+MMCallbackInfo *
+mm_callback_info_uint_new (MMModem *modem,
+ MMModemUIntFn callback,
+ gpointer user_data)
+{
+ g_return_val_if_fail (modem != NULL, NULL);
+
+ return mm_callback_info_new_full (modem, invoke_mm_modem_uint_fn, (GCallback) callback, user_data);
+}
+
+MMCallbackInfo *
+mm_callback_info_string_new (MMModem *modem,
+ MMModemStringFn callback,
+ gpointer user_data)
+{
+ g_return_val_if_fail (modem != NULL, NULL);
+
+ return mm_callback_info_new_full (modem, invoke_mm_modem_string_fn, (GCallback) callback, user_data);
+}
+
+void
+mm_callback_info_set_result (MMCallbackInfo *info,
+ gpointer data,
+ GDestroyNotify destroy)
+{
+ g_return_if_fail (info != NULL);
+
+ mm_callback_info_set_data (info, CALLBACK_INFO_RESULT, data, destroy);
+}
+
+void
+mm_callback_info_set_data (MMCallbackInfo *info,
+ const char *key,
+ gpointer data,
+ GDestroyNotify destroy)
+{
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (key != NULL);
+
+ g_datalist_id_set_data_full (&info->qdata, g_quark_from_string (key), data,
+ data ? destroy : (GDestroyNotify) NULL);
+}
+
+gpointer
+mm_callback_info_get_data (MMCallbackInfo *info, const char *key)
+{
+ GQuark quark;
+
+ g_return_val_if_fail (info != NULL, NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+
+ quark = g_quark_try_string (key);
+
+ return quark ? g_datalist_id_get_data (&info->qdata, quark) : NULL;
+}
+
+MMCallbackInfo *
+mm_callback_info_ref (MMCallbackInfo *info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+ g_return_val_if_fail (info->refcount > 0, NULL);
+
+ info->refcount++;
+ return info;
+}
+
+void
+mm_callback_info_unref (MMCallbackInfo *info)
+{
+ g_return_if_fail (info != NULL);
+
+ info->refcount--;
+ if (info->refcount == 0) {
+ if (info->error)
+ g_error_free (info->error);
+
+ if (info->modem)
+ g_object_weak_unref (G_OBJECT (info->modem), modem_destroyed_cb, info);
+
+ g_datalist_clear (&info->qdata);
+ g_slice_free (MMCallbackInfo, info);
+ }
+}
+
diff --git a/src/mm-callback-info.h b/src/mm-callback-info.h
new file mode 100644
index 0000000..66c2048
--- /dev/null
+++ b/src/mm-callback-info.h
@@ -0,0 +1,74 @@
+/* -*- 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_CALLBACK_INFO_H
+#define MM_CALLBACK_INFO_H
+
+#include "mm-modem.h"
+
+typedef struct _MMCallbackInfo MMCallbackInfo;
+
+typedef void (*MMCallbackInfoInvokeFn) (MMCallbackInfo *info);
+
+struct _MMCallbackInfo {
+ guint32 refcount;
+
+ GData *qdata;
+ MMModem *modem;
+
+ MMCallbackInfoInvokeFn invoke_fn;
+ GCallback callback;
+ gboolean called;
+
+ gpointer user_data;
+ GError *error;
+ guint pending_id;
+};
+
+MMCallbackInfo *mm_callback_info_new_full (MMModem *modem,
+ MMCallbackInfoInvokeFn invoke_fn,
+ GCallback callback,
+ gpointer user_data);
+
+MMCallbackInfo *mm_callback_info_new (MMModem *modem,
+ MMModemFn callback,
+ gpointer user_data);
+
+MMCallbackInfo *mm_callback_info_uint_new (MMModem *modem,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+MMCallbackInfo *mm_callback_info_string_new (MMModem *modem,
+ MMModemStringFn callback,
+ gpointer user_data);
+
+void mm_callback_info_schedule (MMCallbackInfo *info);
+void mm_callback_info_set_result (MMCallbackInfo *info,
+ gpointer data,
+ GDestroyNotify destroy);
+
+void mm_callback_info_set_data (MMCallbackInfo *info,
+ const char *key,
+ gpointer data,
+ GDestroyNotify destroy);
+
+gpointer mm_callback_info_get_data (MMCallbackInfo *info,
+ const char *key);
+
+MMCallbackInfo *mm_callback_info_ref (MMCallbackInfo *info);
+void mm_callback_info_unref (MMCallbackInfo *info);
+
+#endif /* MM_CALLBACK_INFO_H */
+
diff --git a/src/mm-errors.c b/src/mm-errors.c
new file mode 100644
index 0000000..34f56c1
--- /dev/null
+++ b/src/mm-errors.c
@@ -0,0 +1,290 @@
+/* -*- 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.
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ */
+
+#include "mm-errors.h"
+
+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
+
+GQuark
+mm_serial_error_quark (void)
+{
+ static GQuark ret = 0;
+
+ if (ret == 0)
+ ret = g_quark_from_static_string ("mm_serial_error");
+
+ return ret;
+}
+
+GType
+mm_serial_error_get_type (void)
+{
+ static GType etype = 0;
+
+ if (etype == 0) {
+ static const GEnumValue values[] = {
+ ENUM_ENTRY (MM_SERIAL_OPEN_FAILED, "SerialOpenFailed"),
+ ENUM_ENTRY (MM_SERIAL_SEND_FAILED, "SerialSendfailed"),
+ ENUM_ENTRY (MM_SERIAL_RESPONSE_TIMEOUT, "SerialResponseTimeout"),
+ ENUM_ENTRY (MM_SERIAL_OPEN_FAILED_NO_DEVICE, "SerialOpenFailedNoDevice"),
+ { 0, 0, 0 }
+ };
+
+ etype = g_enum_register_static ("MMSerialError", values);
+ }
+
+ return etype;
+}
+
+GQuark
+mm_modem_error_quark (void)
+{
+ static GQuark ret = 0;
+
+ if (ret == 0)
+ ret = g_quark_from_static_string ("mm_modem_error");
+
+ return ret;
+}
+
+GType
+mm_modem_error_get_type (void)
+{
+ static GType etype = 0;
+
+ if (etype == 0) {
+ static const GEnumValue values[] = {
+ ENUM_ENTRY (MM_MODEM_ERROR_GENERAL, "General"),
+ ENUM_ENTRY (MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, "OperationNotSupported"),
+ ENUM_ENTRY (MM_MODEM_ERROR_CONNECTED, "Connected"),
+ ENUM_ENTRY (MM_MODEM_ERROR_DISCONNECTED, "Disconnected"),
+ ENUM_ENTRY (MM_MODEM_ERROR_OPERATION_IN_PROGRESS, "OperationInProgress"),
+ ENUM_ENTRY (MM_MODEM_ERROR_REMOVED, "Removed"),
+ { 0, 0, 0 }
+ };
+
+ etype = g_enum_register_static ("MMModemError", values);
+ }
+
+ return etype;
+}
+
+GQuark
+mm_modem_connect_error_quark (void)
+{
+ static GQuark ret = 0;
+
+ if (ret == 0)
+ ret = g_quark_from_static_string ("mm_modem_connect_error");
+
+ return ret;
+}
+
+GType
+mm_modem_connect_error_get_type (void)
+{
+ static GType etype = 0;
+
+ if (etype == 0) {
+ static const GEnumValue values[] = {
+ ENUM_ENTRY (MM_MODEM_CONNECT_ERROR_NO_CARRIER, "NoCarrier"),
+ ENUM_ENTRY (MM_MODEM_CONNECT_ERROR_NO_DIALTONE, "NoDialtone"),
+ ENUM_ENTRY (MM_MODEM_CONNECT_ERROR_BUSY, "Busy"),
+ ENUM_ENTRY (MM_MODEM_CONNECT_ERROR_NO_ANSWER, "NoAnswer"),
+ { 0, 0, 0 }
+ };
+
+ etype = g_enum_register_static ("MMModemConnectError", values);
+ }
+
+ return etype;
+}
+
+GError *
+mm_modem_connect_error_for_code (int error_code)
+{
+ const char *msg;
+
+ switch (error_code) {
+ case MM_MODEM_CONNECT_ERROR_NO_CARRIER:
+ msg = "No carrier";
+ break;
+ case MM_MODEM_CONNECT_ERROR_NO_DIALTONE:
+ msg = "No dialtone";
+ break;
+ case MM_MODEM_CONNECT_ERROR_BUSY:
+ msg = "Busy";
+ break;
+ case MM_MODEM_CONNECT_ERROR_NO_ANSWER:
+ msg = "No answer";
+ break;
+
+ default:
+ g_warning ("Invalid error code");
+ /* uhm... make something up (yes, ok, lie!). */
+ error_code = MM_MODEM_CONNECT_ERROR_NO_CARRIER;
+ msg = "No carrier";
+ }
+
+ return g_error_new_literal (MM_MODEM_CONNECT_ERROR, error_code, msg);
+}
+
+
+GQuark
+mm_mobile_error_quark (void)
+{
+ static GQuark ret = 0;
+
+ if (ret == 0)
+ ret = g_quark_from_static_string ("mm_mobile_error");
+
+ return ret;
+}
+
+GType
+mm_mobile_error_get_type (void)
+{
+ static GType etype = 0;
+
+ if (etype == 0) {
+ static const GEnumValue values[] = {
+ ENUM_ENTRY (MM_MOBILE_ERROR_PHONE_FAILURE, "PhoneFailure"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_NO_CONNECTION, "NoConnection"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_LINK_RESERVED, "LinkReserved"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_NOT_ALLOWED, "OperationNotAllowed"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_NOT_SUPPORTED, "OperationNotSupported"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_PH_SIM_PIN, "PhSimPinRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_PH_FSIM_PIN, "PhFSimPinRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_PH_FSIM_PUK, "PhFSimPukRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_SIM_NOT_INSERTED, "SimNotInserted"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_SIM_PIN, "SimPinRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_SIM_PUK, "SimPukRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_SIM_FAILURE, "SimFailure"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_SIM_BUSY, "SimBusy"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_SIM_WRONG, "SimWrong"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_WRONG_PASSWORD, "IncorrectPassword"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_SIM_PIN2, "SimPin2Required"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_SIM_PUK2, "SimPuk2Required"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_MEMORY_FULL, "MemoryFull"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_INVALID_INDEX, "InvalidIndex"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_NOT_FOUND, "NotFound"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_MEMORY_FAILURE, "MemoryFailure"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_TEXT_TOO_LONG, "TextTooLong"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_INVALID_CHARS, "InvalidChars"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_DIAL_STRING_TOO_LONG, "DialStringTooLong"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_DIAL_STRING_INVALID, "InvalidDialString"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_NO_NETWORK, "NoNetwork"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_TIMEOUT, "NetworkTimeout"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED, "NetworkNotAllowed"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_PIN, "NetworkPinRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_PUK, "NetworkPukRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_SUBSET_PIN, "NetworkSubsetPinRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_SUBSET_PUK, "NetworkSubsetPukRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_SERVICE_PIN, "ServicePinRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_SERVICE_PUK, "ServicePukRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_CORP_PIN, "CorporatePinRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_CORP_PUK, "CorporatePukRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_HIDDEN_KEY, "HiddenKeyRequired"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_EAP_NOT_SUPPORTED, "EapMethodNotSupported"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_INCORRECT_PARAMS, "IncorrectParams"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_UNKNOWN, "Unknown"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_ILLEGAL_MS, "GprsIllegalMs"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_ILLEGAL_ME, "GprsIllegalMe"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_SERVICE_NOT_ALLOWED, "GprsServiceNotAllowed"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_PLMN_NOT_ALLOWED, "GprsPlmnNotAllowed"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_LOCATION_NOT_ALLOWED, "GprsLocationNotAllowed"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED, "GprsRoamingNotAllowed"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_OPTION_NOT_SUPPORTED, "GprsOptionNotSupported"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_NOT_SUBSCRIBED, "GprsNotSubscribed"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_OUT_OF_ORDER, "GprsOutOfOrder"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE, "GprsPdpAuthFailure"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_UNKNOWN, "GprsUnspecified"),
+ ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_INVALID_CLASS, "GprsInvalidClass"),
+ { 0, 0, 0 }
+ };
+
+ etype = g_enum_register_static ("MMMobileError", values);
+ }
+
+ return etype;
+}
+
+GError *
+mm_mobile_error_for_code (int error_code)
+{
+ const char *msg;
+
+ switch (error_code) {
+ case MM_MOBILE_ERROR_PHONE_FAILURE: msg = "Phone failure"; break;
+ case MM_MOBILE_ERROR_NO_CONNECTION: msg = "No connection to phone"; break;
+ case MM_MOBILE_ERROR_LINK_RESERVED: msg = "Phone-adaptor link reserved"; break;
+ case MM_MOBILE_ERROR_NOT_ALLOWED: msg = "Operation not allowed"; break;
+ case MM_MOBILE_ERROR_NOT_SUPPORTED: msg = "Operation not supported"; break;
+ case MM_MOBILE_ERROR_PH_SIM_PIN: msg = "PH-SIM PIN required"; break;
+ case MM_MOBILE_ERROR_PH_FSIM_PIN: msg = "PH-FSIM PIN required"; break;
+ case MM_MOBILE_ERROR_PH_FSIM_PUK: msg = "PH-FSIM PUK required"; break;
+ case MM_MOBILE_ERROR_SIM_NOT_INSERTED: msg = "SIM not inserted"; break;
+ case MM_MOBILE_ERROR_SIM_PIN: msg = "SIM PIN required"; break;
+ case MM_MOBILE_ERROR_SIM_PUK: msg = "SIM PUK required"; break;
+ case MM_MOBILE_ERROR_SIM_FAILURE: msg = "SIM failure"; break;
+ case MM_MOBILE_ERROR_SIM_BUSY: msg = "SIM busy"; break;
+ case MM_MOBILE_ERROR_SIM_WRONG: msg = "SIM wrong"; break;
+ case MM_MOBILE_ERROR_WRONG_PASSWORD: msg = "Incorrect password"; break;
+ case MM_MOBILE_ERROR_SIM_PIN2: msg = "SIM PIN2 required"; break;
+ case MM_MOBILE_ERROR_SIM_PUK2: msg = "SIM PUK2 required"; break;
+ case MM_MOBILE_ERROR_MEMORY_FULL: msg = "Memory full"; break;
+ case MM_MOBILE_ERROR_INVALID_INDEX: msg = "Invalid index"; break;
+ case MM_MOBILE_ERROR_NOT_FOUND: msg = "Not found"; break;
+ case MM_MOBILE_ERROR_MEMORY_FAILURE: msg = "Memory failure"; break;
+ case MM_MOBILE_ERROR_TEXT_TOO_LONG: msg = "Text string too long"; break;
+ case MM_MOBILE_ERROR_INVALID_CHARS: msg = "Invalid characters in text string"; break;
+ case MM_MOBILE_ERROR_DIAL_STRING_TOO_LONG: msg = "Dial string too long"; break;
+ case MM_MOBILE_ERROR_DIAL_STRING_INVALID: msg = "Invalid characters in dial string"; break;
+ case MM_MOBILE_ERROR_NO_NETWORK: msg = "No network service"; break;
+ case MM_MOBILE_ERROR_NETWORK_TIMEOUT: msg = "Network timeout"; break;
+ case MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED: msg = "Network not allowed - emergency calls only"; break;
+ case MM_MOBILE_ERROR_NETWORK_PIN: msg = "Network personalization PIN required"; break;
+ case MM_MOBILE_ERROR_NETWORK_PUK: msg = "Network personalization PUK required"; break;
+ case MM_MOBILE_ERROR_NETWORK_SUBSET_PIN: msg = "Network subset personalization PIN required"; break;
+ case MM_MOBILE_ERROR_NETWORK_SUBSET_PUK: msg = "Network subset personalization PUK required"; break;
+ case MM_MOBILE_ERROR_SERVICE_PIN: msg = "Service provider personalization PIN required"; break;
+ case MM_MOBILE_ERROR_SERVICE_PUK: msg = "Service provider personalization PUK required"; break;
+ case MM_MOBILE_ERROR_CORP_PIN: msg = "Corporate personalization PIN required"; break;
+ case MM_MOBILE_ERROR_CORP_PUK: msg = "Corporate personalization PUK required"; break;
+ case MM_MOBILE_ERROR_HIDDEN_KEY: msg = "Hidden key required"; break;
+ case MM_MOBILE_ERROR_EAP_NOT_SUPPORTED: msg = "EAP method not supported"; break;
+ case MM_MOBILE_ERROR_INCORRECT_PARAMS: msg = "Incorrect parameters"; break;
+ case MM_MOBILE_ERROR_UNKNOWN: msg = "Unknown error"; break;
+ case MM_MOBILE_ERROR_GPRS_ILLEGAL_MS: msg = "Illegal MS"; break;
+ case MM_MOBILE_ERROR_GPRS_ILLEGAL_ME: msg = "Illegal ME"; break;
+ case MM_MOBILE_ERROR_GPRS_SERVICE_NOT_ALLOWED: msg = "GPRS services not allowed"; break;
+ case MM_MOBILE_ERROR_GPRS_PLMN_NOT_ALLOWED: msg = "PLMN not allowed"; break;
+ case MM_MOBILE_ERROR_GPRS_LOCATION_NOT_ALLOWED: msg = "Location area not allowed"; break;
+ case MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED: msg = "Roaming not allowed in this location area"; break;
+ case MM_MOBILE_ERROR_GPRS_OPTION_NOT_SUPPORTED: msg = "Service option not supported"; break;
+ case MM_MOBILE_ERROR_GPRS_NOT_SUBSCRIBED: msg = "Requested service option not subscribed"; break;
+ case MM_MOBILE_ERROR_GPRS_OUT_OF_ORDER: msg = "Service option temporarily out of order"; break;
+ case MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE: msg = "PDP authentication failure"; break;
+ case MM_MOBILE_ERROR_GPRS_UNKNOWN: msg = "Unspecified GPRS error"; break;
+ case MM_MOBILE_ERROR_GPRS_INVALID_CLASS: msg = "Invalid mobile class"; break;
+ default:
+ g_warning ("Invalid error code");
+ error_code = MM_MOBILE_ERROR_UNKNOWN;
+ msg = "Unknown error";
+ }
+
+ return g_error_new_literal (MM_MOBILE_ERROR, error_code, msg);
+}
diff --git a/src/mm-errors.h b/src/mm-errors.h
new file mode 100644
index 0000000..c02a351
--- /dev/null
+++ b/src/mm-errors.h
@@ -0,0 +1,131 @@
+/* -*- 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.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_MODEM_ERROR_H
+#define MM_MODEM_ERROR_H
+
+#include <glib-object.h>
+
+enum {
+ MM_SERIAL_OPEN_FAILED = 0,
+ MM_SERIAL_SEND_FAILED = 1,
+ MM_SERIAL_RESPONSE_TIMEOUT = 2,
+ MM_SERIAL_OPEN_FAILED_NO_DEVICE = 3
+};
+
+#define MM_SERIAL_ERROR (mm_serial_error_quark ())
+#define MM_TYPE_SERIAL_ERROR (mm_serial_error_get_type ())
+
+GQuark mm_serial_error_quark (void);
+GType mm_serial_error_get_type (void);
+
+
+enum {
+ MM_MODEM_ERROR_GENERAL = 0,
+ MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED = 1,
+ MM_MODEM_ERROR_CONNECTED = 2,
+ MM_MODEM_ERROR_DISCONNECTED = 3,
+ MM_MODEM_ERROR_OPERATION_IN_PROGRESS = 4,
+ MM_MODEM_ERROR_REMOVED = 5
+};
+
+#define MM_MODEM_ERROR (mm_modem_error_quark ())
+#define MM_TYPE_MODEM_ERROR (mm_modem_error_get_type ())
+
+GQuark mm_modem_error_quark (void);
+GType mm_modem_error_get_type (void);
+
+
+enum {
+ MM_MODEM_CONNECT_ERROR_NO_CARRIER = 3,
+ MM_MODEM_CONNECT_ERROR_NO_DIALTONE = 6,
+ MM_MODEM_CONNECT_ERROR_BUSY = 7,
+ MM_MODEM_CONNECT_ERROR_NO_ANSWER = 8,
+};
+
+#define MM_MODEM_CONNECT_ERROR (mm_modem_connect_error_quark ())
+#define MM_TYPE_MODEM_CONNECT_ERROR (mm_modem_connect_error_get_type ())
+
+GQuark mm_modem_connect_error_quark (void);
+GType mm_modem_connect_error_get_type (void);
+GError *mm_modem_connect_error_for_code (int error_code);
+
+
+enum {
+ MM_MOBILE_ERROR_PHONE_FAILURE = 0,
+ MM_MOBILE_ERROR_NO_CONNECTION = 1,
+ MM_MOBILE_ERROR_LINK_RESERVED = 2,
+ MM_MOBILE_ERROR_NOT_ALLOWED = 3,
+ MM_MOBILE_ERROR_NOT_SUPPORTED = 4,
+ MM_MOBILE_ERROR_PH_SIM_PIN = 5,
+ MM_MOBILE_ERROR_PH_FSIM_PIN = 6,
+ MM_MOBILE_ERROR_PH_FSIM_PUK = 7,
+ MM_MOBILE_ERROR_SIM_NOT_INSERTED = 10,
+ MM_MOBILE_ERROR_SIM_PIN = 11,
+ MM_MOBILE_ERROR_SIM_PUK = 12,
+ MM_MOBILE_ERROR_SIM_FAILURE = 13,
+ MM_MOBILE_ERROR_SIM_BUSY = 14,
+ MM_MOBILE_ERROR_SIM_WRONG = 15,
+ MM_MOBILE_ERROR_WRONG_PASSWORD = 16,
+ MM_MOBILE_ERROR_SIM_PIN2 = 17,
+ MM_MOBILE_ERROR_SIM_PUK2 = 18,
+ MM_MOBILE_ERROR_MEMORY_FULL = 20,
+ MM_MOBILE_ERROR_INVALID_INDEX = 21,
+ MM_MOBILE_ERROR_NOT_FOUND = 22,
+ MM_MOBILE_ERROR_MEMORY_FAILURE = 23,
+ MM_MOBILE_ERROR_TEXT_TOO_LONG = 24,
+ MM_MOBILE_ERROR_INVALID_CHARS = 25,
+ MM_MOBILE_ERROR_DIAL_STRING_TOO_LONG = 26,
+ MM_MOBILE_ERROR_DIAL_STRING_INVALID = 27,
+ MM_MOBILE_ERROR_NO_NETWORK = 30,
+ MM_MOBILE_ERROR_NETWORK_TIMEOUT = 31,
+ MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED = 32,
+ MM_MOBILE_ERROR_NETWORK_PIN = 40,
+ MM_MOBILE_ERROR_NETWORK_PUK = 41,
+ MM_MOBILE_ERROR_NETWORK_SUBSET_PIN = 42,
+ MM_MOBILE_ERROR_NETWORK_SUBSET_PUK = 43,
+ MM_MOBILE_ERROR_SERVICE_PIN = 44,
+ MM_MOBILE_ERROR_SERVICE_PUK = 45,
+ MM_MOBILE_ERROR_CORP_PIN = 46,
+ MM_MOBILE_ERROR_CORP_PUK = 47,
+ MM_MOBILE_ERROR_HIDDEN_KEY = 48,
+ MM_MOBILE_ERROR_EAP_NOT_SUPPORTED = 49,
+ MM_MOBILE_ERROR_INCORRECT_PARAMS = 50,
+ MM_MOBILE_ERROR_UNKNOWN = 100,
+
+ MM_MOBILE_ERROR_GPRS_ILLEGAL_MS = 103,
+ MM_MOBILE_ERROR_GPRS_ILLEGAL_ME = 106,
+ MM_MOBILE_ERROR_GPRS_SERVICE_NOT_ALLOWED = 107,
+ MM_MOBILE_ERROR_GPRS_PLMN_NOT_ALLOWED = 111,
+ MM_MOBILE_ERROR_GPRS_LOCATION_NOT_ALLOWED = 112,
+ MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED = 113,
+ MM_MOBILE_ERROR_GPRS_OPTION_NOT_SUPPORTED = 132,
+ MM_MOBILE_ERROR_GPRS_NOT_SUBSCRIBED = 133,
+ MM_MOBILE_ERROR_GPRS_OUT_OF_ORDER = 134,
+ MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE = 149,
+ MM_MOBILE_ERROR_GPRS_UNKNOWN = 148,
+ MM_MOBILE_ERROR_GPRS_INVALID_CLASS = 150
+};
+
+
+#define MM_MOBILE_ERROR (mm_mobile_error_quark ())
+#define MM_TYPE_MOBILE_ERROR (mm_mobile_error_get_type ())
+
+GQuark mm_mobile_error_quark (void);
+GType mm_mobile_error_get_type (void);
+GError *mm_mobile_error_for_code (int error_code);
+
+#endif /* MM_MODEM_ERROR_H */
diff --git a/src/mm-generic-cdma.c b/src/mm-generic-cdma.c
new file mode 100644
index 0000000..50cd86c
--- /dev/null
+++ b/src/mm-generic-cdma.c
@@ -0,0 +1,1831 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "mm-generic-cdma.h"
+#include "mm-modem-cdma.h"
+#include "mm-modem-simple.h"
+#include "mm-serial-port.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+#include "mm-serial-parsers.h"
+
+static void simple_reg_callback (MMModemCdma *modem,
+ MMModemCdmaRegistrationState cdma_1x_reg_state,
+ MMModemCdmaRegistrationState evdo_reg_state,
+ GError *error,
+ gpointer user_data);
+
+static void simple_state_machine (MMModem *modem, GError *error, gpointer user_data);
+
+static void update_enabled_state (MMGenericCdma *self,
+ gboolean stay_connected,
+ MMModemStateReason reason);
+
+static void modem_init (MMModem *modem_class);
+static void modem_cdma_init (MMModemCdma *cdma_class);
+static void modem_simple_init (MMModemSimple *class);
+
+G_DEFINE_TYPE_EXTENDED (MMGenericCdma, mm_generic_cdma, MM_TYPE_MODEM_BASE, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_CDMA, modem_cdma_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_SIMPLE, modem_simple_init))
+
+#define MM_GENERIC_CDMA_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_GENERIC_CDMA, MMGenericCdmaPrivate))
+
+typedef struct {
+ guint32 cdma1x_quality;
+ guint32 evdo_quality;
+ gboolean valid;
+ gboolean evdo_rev0;
+ gboolean evdo_revA;
+ gboolean reg_try_css;
+
+ MMModemCdmaRegistrationState cdma_1x_reg_state;
+ MMModemCdmaRegistrationState evdo_reg_state;
+
+ guint reg_tries;
+ guint reg_retry_id;
+ guint reg_state_changed_id;
+ MMCallbackInfo *simple_connect_info;
+
+ MMSerialPort *primary;
+ MMSerialPort *secondary;
+ MMPort *data;
+} MMGenericCdmaPrivate;
+
+enum {
+ PROP_0,
+ PROP_EVDO_REV0,
+ PROP_EVDO_REVA,
+ PROP_REG_TRY_CSS,
+ LAST_PROP
+};
+
+MMModem *
+mm_generic_cdma_new (const char *device,
+ const char *driver,
+ const char *plugin,
+ gboolean evdo_rev0,
+ gboolean evdo_revA)
+{
+ g_return_val_if_fail (device != NULL, NULL);
+ g_return_val_if_fail (driver != NULL, NULL);
+ g_return_val_if_fail (plugin != NULL, NULL);
+
+ return MM_MODEM (g_object_new (MM_TYPE_GENERIC_CDMA,
+ MM_MODEM_MASTER_DEVICE, device,
+ MM_MODEM_DRIVER, driver,
+ MM_MODEM_PLUGIN, plugin,
+ MM_GENERIC_CDMA_EVDO_REV0, evdo_rev0,
+ MM_GENERIC_CDMA_EVDO_REVA, evdo_revA,
+ NULL));
+}
+
+/*****************************************************************************/
+
+static void
+check_valid (MMGenericCdma *self)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+ gboolean new_valid = FALSE;
+
+ if (priv->primary && priv->data)
+ new_valid = TRUE;
+
+ mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid);
+}
+
+static gboolean
+owns_port (MMModem *modem, const char *subsys, const char *name)
+{
+ return !!mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name);
+}
+
+MMPort *
+mm_generic_cdma_grab_port (MMGenericCdma *self,
+ const char *subsys,
+ const char *name,
+ MMPortType suggested_type,
+ gpointer user_data,
+ GError **error)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+ MMPortType ptype = MM_PORT_TYPE_IGNORED;
+ MMPort *port;
+
+ g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), FALSE);
+ if (priv->primary)
+ g_return_val_if_fail (suggested_type != MM_PORT_TYPE_PRIMARY, FALSE);
+
+ if (!strcmp (subsys, "tty")) {
+ if (suggested_type != MM_PORT_TYPE_UNKNOWN)
+ ptype = suggested_type;
+ else {
+ if (!priv->primary)
+ ptype = MM_PORT_TYPE_PRIMARY;
+ else if (!priv->secondary)
+ ptype = MM_PORT_TYPE_SECONDARY;
+ }
+ }
+
+ port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype);
+ if (port && MM_IS_SERIAL_PORT (port)) {
+ g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL);
+ mm_serial_port_set_response_parser (MM_SERIAL_PORT (port),
+ mm_serial_parser_v1_parse,
+ mm_serial_parser_v1_new (),
+ mm_serial_parser_v1_destroy);
+
+ if (ptype == MM_PORT_TYPE_PRIMARY) {
+ priv->primary = MM_SERIAL_PORT (port);
+ if (!priv->data) {
+ priv->data = port;
+ g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE);
+ }
+ check_valid (self);
+ } else if (ptype == MM_PORT_TYPE_SECONDARY)
+ priv->secondary = MM_SERIAL_PORT (port);
+ } else {
+ /* Net device (if any) is the preferred data port */
+ if (!priv->data || MM_IS_SERIAL_PORT (priv->data)) {
+ priv->data = port;
+ g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE);
+ check_valid (self);
+ }
+ }
+
+ return port;
+}
+
+static gboolean
+grab_port (MMModem *modem,
+ const char *subsys,
+ const char *name,
+ MMPortType suggested_type,
+ gpointer user_data,
+ GError **error)
+{
+ return !!mm_generic_cdma_grab_port (MM_GENERIC_CDMA (modem), subsys, name, suggested_type, user_data, error);
+}
+
+static void
+release_port (MMModem *modem, const char *subsys, const char *name)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
+ MMPort *port;
+
+ port = mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name);
+ if (!port)
+ return;
+
+ if (port == MM_PORT (priv->primary)) {
+ mm_modem_base_remove_port (MM_MODEM_BASE (modem), port);
+ priv->primary = NULL;
+ }
+
+ if (port == priv->data) {
+ priv->data = NULL;
+ g_object_notify (G_OBJECT (modem), MM_MODEM_DATA_DEVICE);
+ }
+
+ if (port == MM_PORT (priv->secondary)) {
+ mm_modem_base_remove_port (MM_MODEM_BASE (modem), port);
+ priv->secondary = NULL;
+ }
+
+ check_valid (MM_GENERIC_CDMA (modem));
+}
+
+MMSerialPort *
+mm_generic_cdma_get_port (MMGenericCdma *modem,
+ MMPortType ptype)
+{
+ g_return_val_if_fail (MM_IS_GENERIC_CDMA (modem), NULL);
+ g_return_val_if_fail (ptype != MM_PORT_TYPE_UNKNOWN, NULL);
+
+ if (ptype == MM_PORT_TYPE_PRIMARY)
+ return MM_GENERIC_CDMA_GET_PRIVATE (modem)->primary;
+ else if (ptype == MM_PORT_TYPE_SECONDARY)
+ return MM_GENERIC_CDMA_GET_PRIVATE (modem)->secondary;
+
+ return NULL;
+}
+
+/*****************************************************************************/
+
+void
+mm_generic_cdma_set_1x_registration_state (MMGenericCdma *self,
+ MMModemCdmaRegistrationState new_state)
+{
+ MMGenericCdmaPrivate *priv;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (MM_IS_GENERIC_CDMA (self));
+
+ priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+
+ if (priv->cdma_1x_reg_state != new_state) {
+ priv->cdma_1x_reg_state = new_state;
+
+ update_enabled_state (self, TRUE, MM_MODEM_STATE_REASON_NONE);
+ mm_modem_cdma_emit_registration_state_changed (MM_MODEM_CDMA (self),
+ priv->cdma_1x_reg_state,
+ priv->evdo_reg_state);
+ }
+}
+
+void
+mm_generic_cdma_set_evdo_registration_state (MMGenericCdma *self,
+ MMModemCdmaRegistrationState new_state)
+{
+ MMGenericCdmaPrivate *priv;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (MM_IS_GENERIC_CDMA (self));
+
+ priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+
+ if (priv->evdo_reg_state == new_state)
+ return;
+
+ /* Don't update EVDO state if the card doesn't support it */
+ if ( priv->evdo_rev0
+ || priv->evdo_revA
+ || (new_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)) {
+ priv->evdo_reg_state = new_state;
+
+ update_enabled_state (self, TRUE, MM_MODEM_STATE_REASON_NONE);
+ mm_modem_cdma_emit_registration_state_changed (MM_MODEM_CDMA (self),
+ priv->cdma_1x_reg_state,
+ priv->evdo_reg_state);
+ }
+}
+
+MMModemCdmaRegistrationState
+mm_generic_cdma_1x_get_registration_state_sync (MMGenericCdma *self)
+{
+ g_return_val_if_fail (self != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+ g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+
+ return MM_GENERIC_CDMA_GET_PRIVATE (self)->cdma_1x_reg_state;
+}
+
+MMModemCdmaRegistrationState
+mm_generic_cdma_evdo_get_registration_state_sync (MMGenericCdma *self)
+{
+ g_return_val_if_fail (self != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+ g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+
+ return MM_GENERIC_CDMA_GET_PRIVATE (self)->evdo_reg_state;
+}
+
+/*****************************************************************************/
+
+static void
+update_enabled_state (MMGenericCdma *self,
+ gboolean stay_connected,
+ MMModemStateReason reason)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+
+ /* While connected we don't want registration status changes to change
+ * the modem's state away from CONNECTED.
+ */
+ if (stay_connected && (mm_modem_get_state (MM_MODEM (self)) >= MM_MODEM_STATE_DISCONNECTING))
+ return;
+
+ if ( priv->cdma_1x_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN
+ || priv->evdo_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_REGISTERED, reason);
+ else
+ mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_ENABLED, reason);
+}
+
+static void
+registration_cleanup (MMGenericCdma *self, GQuark error_class, guint32 error_num)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ priv->reg_tries = 0;
+
+ if (priv->reg_state_changed_id) {
+ g_signal_handler_disconnect (self, priv->reg_state_changed_id);
+ priv->reg_state_changed_id = 0;
+ }
+
+ if (priv->reg_retry_id) {
+ g_source_remove (priv->reg_retry_id);
+ priv->reg_retry_id = 0;
+ }
+
+ /* Return an error to any explicit callers of simple_connect */
+ if (priv->simple_connect_info && error_class) {
+ error = g_error_new_literal (error_class, error_num,
+ "Connection attempt terminated");
+ simple_state_machine (MM_MODEM (self), error, priv->simple_connect_info);
+ g_error_free (error);
+ }
+ priv->simple_connect_info = NULL;
+}
+
+static void
+enable_all_done (MMModem *modem, GError *error, gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+ MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+
+ if (error)
+ info->error = g_error_copy (error);
+ else {
+ /* Open up the second port, if one exists */
+ if (priv->secondary) {
+ if (!mm_serial_port_open (priv->secondary, &info->error)) {
+ g_assert (info->error);
+ goto out;
+ }
+ }
+
+ update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE);
+ }
+
+out:
+ if (info->error) {
+ mm_modem_set_state (MM_MODEM (info->modem),
+ MM_MODEM_STATE_DISABLED,
+ MM_MODEM_STATE_REASON_NONE);
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+enable_error_reporting_done (MMSerialPort *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 (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ if (error) {
+ mm_modem_set_state (MM_MODEM (info->modem),
+ MM_MODEM_STATE_DISABLED,
+ MM_MODEM_STATE_REASON_NONE);
+
+ 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_serial_port_queue_command (port, "+CMEE=1", 3, enable_error_reporting_done, user_data);
+ }
+}
+
+static void
+flash_done (MMSerialPort *port, GError *error, gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ if (error) {
+ mm_modem_set_state (MM_MODEM (info->modem),
+ MM_MODEM_STATE_DISABLED,
+ MM_MODEM_STATE_REASON_NONE);
+
+ /* Flash failed for some reason */
+ info->error = g_error_copy (error);
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ mm_serial_port_queue_command (port, "Z E0 V1 X4 &C1", 3, init_done, user_data);
+}
+
+static void
+enable (MMModem *modem,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericCdma *self = MM_GENERIC_CDMA (modem);
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new (modem, callback, user_data);
+
+ if (!mm_serial_port_open (priv->primary, &info->error)) {
+ g_assert (info->error);
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ mm_modem_set_state (MM_MODEM (info->modem),
+ MM_MODEM_STATE_ENABLING,
+ MM_MODEM_STATE_REASON_NONE);
+
+ mm_serial_port_flash (priv->primary, 100, flash_done, info);
+}
+
+static void
+disable_set_previous_state (MMModem *modem, MMCallbackInfo *info)
+{
+ MMModemState prev_state;
+
+ /* Reset old state since the operation failed */
+ prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_CDMA_PREV_STATE_TAG));
+ mm_modem_set_state (modem, prev_state, MM_MODEM_STATE_REASON_NONE);
+}
+
+static void
+disable_all_done (MMModem *modem, GError *error, gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+
+ info->error = mm_modem_check_removed (modem, error);
+ if (info->error) {
+ if (modem)
+ disable_set_previous_state (modem, info);
+ } else {
+ MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+
+ mm_serial_port_close (priv->primary);
+ mm_modem_set_state (modem, MM_MODEM_STATE_DISABLED, MM_MODEM_STATE_REASON_NONE);
+
+ priv->cdma_1x_reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ priv->evdo_reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+disable_flash_done (MMSerialPort *port,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+ MMGenericCdma *self;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->error) {
+ if (info->modem)
+ disable_set_previous_state (info->modem, info);
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ self = MM_GENERIC_CDMA (info->modem);
+
+ if (MM_GENERIC_CDMA_GET_CLASS (self)->post_disable)
+ MM_GENERIC_CDMA_GET_CLASS (self)->post_disable (self, disable_all_done, info);
+ else
+ disable_all_done (MM_MODEM (self), NULL, info);
+}
+
+static void
+disable (MMModem *modem,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericCdma *self = MM_GENERIC_CDMA (modem);
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+ MMCallbackInfo *info;
+ MMModemState state;
+
+ /* Tear down any ongoing registration */
+ registration_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL);
+
+ info = mm_callback_info_new (modem, callback, user_data);
+
+ /* Cache the previous state so we can reset it if the operation fails */
+ state = mm_modem_get_state (modem);
+ mm_callback_info_set_data (info,
+ MM_GENERIC_CDMA_PREV_STATE_TAG,
+ GUINT_TO_POINTER (state),
+ NULL);
+
+ if (priv->secondary)
+ mm_serial_port_close (priv->secondary);
+
+ mm_modem_set_state (MM_MODEM (info->modem),
+ MM_MODEM_STATE_DISABLING,
+ MM_MODEM_STATE_REASON_NONE);
+
+ if (mm_port_get_connected (MM_PORT (priv->primary)))
+ mm_serial_port_flash (priv->primary, 1000, disable_flash_done, info);
+ else
+ disable_flash_done (priv->primary, NULL, info);
+}
+
+static void
+dial_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->error) {
+ if (info->modem)
+ update_enabled_state (MM_GENERIC_CDMA (info->modem), FALSE, MM_MODEM_STATE_REASON_NONE);
+ } else {
+ MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+
+ /* Clear reg tries; we're obviously registered by this point */
+ registration_cleanup (self, 0, 0);
+
+ mm_port_set_connected (priv->data, TRUE);
+ mm_modem_set_state (info->modem, MM_MODEM_STATE_CONNECTED, MM_MODEM_STATE_REASON_NONE);
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+connect (MMModem *modem,
+ const char *number,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ char *command;
+
+ mm_modem_set_state (modem, MM_MODEM_STATE_CONNECTING, MM_MODEM_STATE_REASON_NONE);
+
+ info = mm_callback_info_new (modem, callback, user_data);
+ command = g_strconcat ("DT", number, NULL);
+ mm_serial_port_queue_command (priv->primary, command, 90, dial_done, info);
+ g_free (command);
+}
+
+static void
+disconnect_flash_done (MMSerialPort *port,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMModemState prev_state;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->error) {
+ if (info->modem) {
+ /* Reset old state since the operation failed */
+ prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_CDMA_PREV_STATE_TAG));
+ mm_modem_set_state (MM_MODEM (info->modem),
+ prev_state,
+ MM_MODEM_STATE_REASON_NONE);
+ }
+ } else {
+ mm_port_set_connected (MM_GENERIC_CDMA_GET_PRIVATE (info->modem)->data, FALSE);
+ update_enabled_state (MM_GENERIC_CDMA (info->modem), FALSE, MM_MODEM_STATE_REASON_NONE);
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+disconnect (MMModem *modem,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ MMModemState state;
+
+ g_return_if_fail (priv->primary != NULL);
+
+ info = mm_callback_info_new (modem, callback, user_data);
+
+ /* Cache the previous state so we can reset it if the operation fails */
+ state = mm_modem_get_state (modem);
+ mm_callback_info_set_data (info,
+ MM_GENERIC_CDMA_PREV_STATE_TAG,
+ GUINT_TO_POINTER (state),
+ NULL);
+
+ mm_modem_set_state (modem, MM_MODEM_STATE_DISCONNECTING, MM_MODEM_STATE_REASON_NONE);
+ mm_serial_port_flash (priv->primary, 1000, disconnect_flash_done, info);
+}
+
+static void
+card_info_invoke (MMCallbackInfo *info)
+{
+ MMModemInfoFn callback = (MMModemInfoFn) info->callback;
+
+ callback (info->modem,
+ (char *) mm_callback_info_get_data (info, "card-info-manufacturer"),
+ (char *) mm_callback_info_get_data (info, "card-info-model"),
+ (char *) mm_callback_info_get_data (info, "card-info-version"),
+ info->error, info->user_data);
+}
+
+static const char *
+strip_response (const char *resp, const char *cmd)
+{
+ const char *p = resp;
+
+ if (p) {
+ if (!strncmp (p, cmd, strlen (cmd)))
+ p += strlen (cmd);
+ while (*p == ' ')
+ p++;
+ }
+ return p;
+}
+
+static void
+get_version_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ const char *p;
+
+ if (!error) {
+ p = strip_response (response->str, "+GMR:");
+ mm_callback_info_set_data (info, "card-info-version", g_strdup (p), g_free);
+ } else if (!info->error)
+ info->error = g_error_copy (error);
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+get_model_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ const char *p;
+
+ if (!error) {
+ p = strip_response (response->str, "+GMM:");
+ mm_callback_info_set_data (info, "card-info-model", g_strdup (p), g_free);
+ } else if (!info->error)
+ info->error = g_error_copy (error);
+}
+
+static void
+get_manufacturer_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ const char *p;
+
+ if (!error) {
+ p = strip_response (response->str, "+GMI:");
+ mm_callback_info_set_data (info, "card-info-manufacturer", g_strdup (p), g_free);
+ } else
+ info->error = g_error_copy (error);
+}
+
+static void
+get_card_info (MMModem *modem,
+ MMModemInfoFn callback,
+ gpointer user_data)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ MMSerialPort *port = priv->primary;
+
+ info = mm_callback_info_new_full (MM_MODEM (modem),
+ card_info_invoke,
+ G_CALLBACK (callback),
+ user_data);
+
+ if (mm_port_get_connected (MM_PORT (priv->primary))) {
+ if (!priv->secondary) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED,
+ "Cannot modem info while connected");
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ /* Use secondary port if primary is connected */
+ port = priv->secondary;
+ }
+
+ mm_serial_port_queue_command_cached (port, "+GMI", 3, get_manufacturer_done, info);
+ mm_serial_port_queue_command_cached (port, "+GMM", 3, get_model_done, info);
+ mm_serial_port_queue_command_cached (port, "+GMR", 3, get_version_done, info);
+}
+
+/*****************************************************************************/
+
+void
+mm_generic_cdma_update_cdma1x_quality (MMGenericCdma *self, guint32 quality)
+{
+ MMGenericCdmaPrivate *priv;
+
+ g_return_if_fail (MM_IS_GENERIC_CDMA (self));
+ g_return_if_fail (quality >= 0 && quality <= 100);
+
+ priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+ if (priv->cdma1x_quality != quality) {
+ priv->cdma1x_quality = quality;
+ mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (self), quality);
+ }
+}
+
+void
+mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality)
+{
+ MMGenericCdmaPrivate *priv;
+
+ g_return_if_fail (MM_IS_GENERIC_CDMA (self));
+ g_return_if_fail (quality >= 0 && quality <= 100);
+
+ priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+ if (priv->evdo_quality != quality) {
+ priv->evdo_quality = quality;
+ // FIXME: emit a signal
+ }
+}
+
+#define CSQ2_TRIED "csq?-tried"
+
+static void
+get_signal_quality_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMGenericCdmaPrivate *priv;
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ char *reply = response->str;
+
+ if (error) {
+ if (mm_callback_info_get_data (info, CSQ2_TRIED))
+ info->error = g_error_copy (error);
+ else {
+ /* Some modems want +CSQ, others want +CSQ?, and some of both types
+ * will return ERROR if they don't get the command they want. So
+ * try the other command if the first one fails.
+ */
+ mm_callback_info_set_data (info, CSQ2_TRIED, GUINT_TO_POINTER (1), NULL);
+ mm_serial_port_queue_command (port, "+CSQ?", 3, get_signal_quality_done, info);
+ return;
+ }
+ } else {
+ int quality, ber;
+
+ /* Got valid reply */
+ if (!strncmp (reply, "+CSQ: ", 6))
+ reply += 6;
+
+ if (sscanf (reply, "%d, %d", &quality, &ber)) {
+ /* 99 means unknown/no service */
+ if (quality == 99) {
+ info->error = g_error_new_literal (MM_MOBILE_ERROR,
+ MM_MOBILE_ERROR_NO_NETWORK,
+ "No service");
+ } else {
+ /* Normalize the quality */
+ quality = CLAMP (quality, 0, 31) * 100 / 31;
+
+ priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
+ mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL);
+ if (priv->cdma1x_quality != quality) {
+ priv->cdma1x_quality = quality;
+ mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (info->modem), quality);
+ }
+ }
+ } else
+ info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "%s", "Could not parse signal quality results");
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+get_signal_quality (MMModemCdma *modem,
+ MMModemUIntFn callback,
+ gpointer user_data)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ MMSerialPort *port = priv->primary;
+
+ if (mm_port_get_connected (MM_PORT (priv->primary))) {
+ if (!priv->secondary) {
+ g_message ("Returning saved signal quality %d", priv->cdma1x_quality);
+ callback (MM_MODEM (modem), priv->cdma1x_quality, NULL, user_data);
+ return;
+ }
+
+ /* Use secondary port if primary is connected */
+ port = priv->secondary;
+ }
+
+ info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data);
+ mm_serial_port_queue_command (port, "+CSQ", 3, get_signal_quality_done, info);
+}
+
+static void
+get_string_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ const char *p;
+
+ if (error)
+ info->error = g_error_copy (error);
+ else {
+ p = strip_response (response->str, "+GSN:");
+ mm_callback_info_set_result (info, g_strdup (p), g_free);
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+get_esn (MMModemCdma *modem,
+ MMModemStringFn callback,
+ gpointer user_data)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ GError *error;
+ MMSerialPort *port = priv->primary;
+
+ if (mm_port_get_connected (MM_PORT (priv->primary))) {
+ if (!priv->secondary) {
+ error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED,
+ "Cannot get ESN while connected");
+ callback (MM_MODEM (modem), NULL, error, user_data);
+ g_error_free (error);
+ return;
+ }
+
+ /* Use secondary port if primary is connected */
+ port = priv->secondary;
+ }
+
+ info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
+ mm_serial_port_queue_command_cached (port, "+GSN", 3, get_string_done, info);
+}
+
+static void
+serving_system_invoke (MMCallbackInfo *info)
+{
+ MMModemCdmaServingSystemFn callback = (MMModemCdmaServingSystemFn) info->callback;
+
+ callback (MM_MODEM_CDMA (info->modem),
+ GPOINTER_TO_UINT (mm_callback_info_get_data (info, "class")),
+ (unsigned char) GPOINTER_TO_UINT (mm_callback_info_get_data (info, "band")),
+ GPOINTER_TO_UINT (mm_callback_info_get_data (info, "sid")),
+ info->error,
+ info->user_data);
+}
+
+static int
+normalize_class (const char *orig_class)
+{
+ char class;
+
+ g_return_val_if_fail (orig_class != NULL, '0');
+
+ class = toupper (orig_class[0]);
+
+ /* Cellular (850MHz) */
+ if (class == '1' || class == 'C')
+ return 1;
+ /* PCS (1900MHz) */
+ if (class == '2' || class == 'P')
+ return 2;
+
+ /* Unknown/not registered */
+ return 0;
+}
+
+static char
+normalize_band (const char *long_band, int *out_class)
+{
+ char band;
+
+ g_return_val_if_fail (long_band != NULL, 'Z');
+
+ /* There are two response formats for the band; one includes the band
+ * class and the other doesn't. For modems that include the band class
+ * (ex Novatel S720) you'll see "Px" or "Cx" depending on whether the modem
+ * is registered on a PCS/1900 (P) or Cellular/850 (C) system.
+ */
+ band = toupper (long_band[0]);
+
+ /* Possible band class in first position; return it */
+ if (band == 'C' || band == 'P') {
+ char tmp[2] = { band, '\0' };
+
+ *out_class = normalize_class (tmp);
+ band = toupper (long_band[1]);
+ }
+
+ /* normalize to A - F, and Z */
+ if (band >= 'A' && band <= 'F')
+ return band;
+
+ /* Unknown/not registered */
+ return 'Z';
+}
+
+static int
+convert_sid (const char *sid)
+{
+ long int tmp_sid;
+
+ g_return_val_if_fail (sid != NULL, 99999);
+
+ errno = 0;
+ tmp_sid = strtol (sid, NULL, 10);
+ if ((errno == EINVAL) || (errno == ERANGE))
+ return 99999;
+ else if (tmp_sid < G_MININT || tmp_sid > G_MAXINT)
+ return 99999;
+
+ return (int) tmp_sid;
+}
+
+static void
+serving_system_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ char *reply = response->str;
+ int class = 0, sid = 99999, num;
+ unsigned char band = 'Z';
+ gboolean success = FALSE;
+
+ if (error) {
+ info->error = g_error_copy (error);
+ goto out;
+ }
+
+ if (strstr (reply, "+CSS: "))
+ reply += 6;
+
+ num = sscanf (reply, "? , %d", &sid);
+ if (num == 1) {
+ /* UTStarcom and Huawei modems that use IS-707-A format; note that
+ * this format obviously doesn't have other indicators like band and
+ * class and thus SID 0 will be reported as "no service" (see below).
+ */
+ class = 0;
+ band = 'Z';
+ success = TRUE;
+ } else {
+ GRegex *r;
+ GMatchInfo *match_info;
+ int override_class = 0;
+
+ /* Format is "<band_class>,<band>,<sid>" */
+ r = g_regex_new ("\\s*([^,]*?)\\s*,\\s*([^,]*?)\\s*,\\s*(\\d+)", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ if (!r) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Could not parse Serving System results (regex creation failed).");
+ goto out;
+ }
+
+ g_regex_match (r, reply, 0, &match_info);
+ if (g_match_info_get_match_count (match_info) >= 3) {
+ char *str;
+
+ /* band class */
+ str = g_match_info_fetch (match_info, 1);
+ class = normalize_class (str);
+ g_free (str);
+
+ /* band */
+ str = g_match_info_fetch (match_info, 2);
+ band = normalize_band (str, &override_class);
+ if (override_class)
+ class = override_class;
+ g_free (str);
+
+ /* sid */
+ str = g_match_info_fetch (match_info, 3);
+ sid = convert_sid (str);
+ g_free (str);
+
+ success = TRUE;
+ }
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+ }
+
+ if (success) {
+ gboolean class_ok = FALSE, band_ok = FALSE;
+
+ /* Normalize the SID */
+ if (sid < 0 || sid > 32767)
+ sid = 99999;
+
+ if (class == 1 || class == 2)
+ class_ok = TRUE;
+ if (band != 'Z')
+ band_ok = TRUE;
+
+ /* Return 'no service' if none of the elements of the +CSS response
+ * indicate that the modem has service. Note that this allows SID 0
+ * when at least one of the other elements indicates service.
+ * Normally we'd treat SID 0 as 'no service' but some modems
+ * (Sierra 5725) sometimes return SID 0 even when registered.
+ */
+ if (sid == 0 && !class_ok && !band_ok)
+ sid = 99999;
+
+ /* 99999 means unknown/no service */
+ if (sid == 99999) {
+ /* NOTE: update reg_state_css_response() if this error changes */
+ info->error = g_error_new_literal (MM_MOBILE_ERROR,
+ MM_MOBILE_ERROR_NO_NETWORK,
+ "No service");
+ } else {
+ mm_callback_info_set_data (info, "class", GUINT_TO_POINTER (class), NULL);
+ mm_callback_info_set_data (info, "band", GUINT_TO_POINTER ((guint32) band), NULL);
+ mm_callback_info_set_data (info, "sid", GUINT_TO_POINTER (sid), NULL);
+ }
+ } else {
+ info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Could not parse Serving System results.");
+ }
+
+ out:
+ mm_callback_info_schedule (info);
+}
+
+static void
+get_serving_system (MMModemCdma *modem,
+ MMModemCdmaServingSystemFn callback,
+ gpointer user_data)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ GError *error;
+ MMSerialPort *port = priv->primary;
+
+ if (mm_port_get_connected (MM_PORT (priv->primary))) {
+ if (!priv->secondary) {
+ error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED,
+ "Cannot get serving system while connected");
+ callback (modem, 0, 0, 0, error, user_data);
+ g_error_free (error);
+ return;
+ }
+
+ /* Use secondary port if primary is connected */
+ port = priv->secondary;
+ }
+
+ info = mm_callback_info_new_full (MM_MODEM (modem),
+ serving_system_invoke,
+ G_CALLBACK (callback),
+ user_data);
+
+ mm_serial_port_queue_command (port, "+CSS?", 3, serving_system_done, info);
+}
+
+#define CDMA_1X_STATE_TAG "cdma-1x-reg-state"
+#define EVDO_STATE_TAG "evdo-reg-state"
+
+void
+mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info,
+ MMModemCdmaRegistrationState new_state)
+{
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (info->modem != NULL);
+ g_return_if_fail (MM_IS_GENERIC_CDMA (info->modem));
+
+ mm_callback_info_set_data (info, CDMA_1X_STATE_TAG, GUINT_TO_POINTER (new_state), NULL);
+}
+
+void
+mm_generic_cdma_query_reg_state_set_callback_evdo_state (MMCallbackInfo *info,
+ MMModemCdmaRegistrationState new_state)
+{
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (info->modem != NULL);
+ g_return_if_fail (MM_IS_GENERIC_CDMA (info->modem));
+
+ mm_callback_info_set_data (info, EVDO_STATE_TAG, GUINT_TO_POINTER (new_state), NULL);
+}
+
+static void
+registration_state_invoke (MMCallbackInfo *info)
+{
+ MMModemCdmaRegistrationStateFn callback = (MMModemCdmaRegistrationStateFn) info->callback;
+
+ /* note: This is the MMModemCdma interface callback */
+ callback (MM_MODEM_CDMA (info->modem),
+ GPOINTER_TO_UINT (mm_callback_info_get_data (info, CDMA_1X_STATE_TAG)),
+ GPOINTER_TO_UINT (mm_callback_info_get_data (info, EVDO_STATE_TAG)),
+ info->error,
+ info->user_data);
+}
+
+MMCallbackInfo *
+mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self,
+ MMModemCdmaRegistrationStateFn callback,
+ gpointer user_data)
+{
+ MMGenericCdmaPrivate *priv;
+ MMCallbackInfo *info;
+
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), NULL);
+ g_return_val_if_fail (callback != NULL, NULL);
+
+ info = mm_callback_info_new_full (MM_MODEM (self),
+ registration_state_invoke,
+ G_CALLBACK (callback),
+ user_data);
+
+ /* Fill with current state */
+ priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+ mm_callback_info_set_data (info,
+ CDMA_1X_STATE_TAG,
+ GUINT_TO_POINTER (priv->cdma_1x_reg_state),
+ NULL);
+ mm_callback_info_set_data (info,
+ EVDO_STATE_TAG,
+ GUINT_TO_POINTER (priv->evdo_reg_state),
+ NULL);
+ return info;
+}
+
+static void
+set_callback_1x_state_helper (MMCallbackInfo *info,
+ MMModemCdmaRegistrationState new_state)
+{
+ MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
+
+ mm_generic_cdma_set_1x_registration_state (self, new_state);
+ mm_generic_cdma_query_reg_state_set_callback_1x_state (info, priv->cdma_1x_reg_state);
+}
+
+static void
+set_callback_evdo_state_helper (MMCallbackInfo *info,
+ MMModemCdmaRegistrationState new_state)
+{
+ MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
+
+ mm_generic_cdma_set_evdo_registration_state (self, new_state);
+ mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, priv->evdo_reg_state);
+}
+
+static void
+reg_state_query_done (MMModemCdma *cdma,
+ MMModemCdmaRegistrationState cdma_1x_reg_state,
+ MMModemCdmaRegistrationState evdo_reg_state,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ if (error)
+ info->error = g_error_copy (error);
+ else {
+ set_callback_1x_state_helper (info, cdma_1x_reg_state);
+ set_callback_evdo_state_helper (info, evdo_reg_state);
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+query_subclass_registration_state (MMGenericCdma *self, MMCallbackInfo *info)
+{
+ /* Let subclasses figure out roaming and detailed registration state */
+ if (MM_GENERIC_CDMA_GET_CLASS (self)->query_registration_state) {
+ MM_GENERIC_CDMA_GET_CLASS (self)->query_registration_state (self,
+ reg_state_query_done,
+ info);
+ } else {
+ /* Or if the subclass doesn't implement more specific checking,
+ * assume we're registered.
+ */
+ reg_state_query_done (MM_MODEM_CDMA (self),
+ MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED,
+ MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED,
+ NULL,
+ info);
+ }
+}
+
+static void
+reg_state_css_response (MMModemCdma *cdma,
+ guint32 class,
+ unsigned char band,
+ guint32 sid,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ /* We'll get an error if the SID isn't valid, so detect that and
+ * report unknown registration state.
+ */
+ if (error) {
+ if ( (error->domain == MM_MOBILE_ERROR)
+ && (error->code == MM_MOBILE_ERROR_NO_NETWORK)) {
+ set_callback_1x_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+ set_callback_evdo_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+ } else {
+ /* Some other error parsing CSS results */
+ info->error = g_error_copy (error);
+ }
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ query_subclass_registration_state (MM_GENERIC_CDMA (info->modem), info);
+}
+
+static void
+get_analog_digital_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ const char *reply;
+ long int int_cad;
+
+ if (error) {
+ info->error = g_error_copy (error);
+ goto error;
+ }
+
+ /* Strip any leading command tag and spaces */
+ reply = strip_response (response->str, "+CAD:");
+
+ errno = 0;
+ int_cad = strtol (reply, NULL, 10);
+ if ((errno == EINVAL) || (errno == ERANGE)) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Failed to parse +CAD response");
+ goto error;
+ }
+
+ if (int_cad == 1) { /* 1 == CDMA service */
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
+
+ /* Now that we have some sort of service, check if the the device is
+ * registered on the network.
+ */
+
+ /* Some devices key the AT+CSS? response off the 1X state, but if the
+ * device has EVDO service but no 1X service, then reading AT+CSS? will
+ * error out too early. Let subclasses that know that their AT+CSS?
+ * response is wrong in this case handle more specific registration
+ * themselves; if they do, they'll set priv->reg_try_css to FALSE.
+ */
+ if (priv->reg_try_css) {
+ get_serving_system (MM_MODEM_CDMA (info->modem),
+ reg_state_css_response,
+ info);
+ } else {
+ /* Subclass knows that AT+CSS? will respond incorrectly to EVDO
+ * state, so skip AT+CSS? query.
+ */
+ query_subclass_registration_state (MM_GENERIC_CDMA (info->modem), info);
+ }
+ return;
+ } 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);
+ }
+
+error:
+ mm_callback_info_schedule (info);
+}
+
+static void
+get_registration_state (MMModemCdma *modem,
+ MMModemCdmaRegistrationStateFn callback,
+ gpointer user_data)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ MMSerialPort *port = priv->primary;
+
+ if (mm_port_get_connected (MM_PORT (priv->primary))) {
+ if (!priv->secondary) {
+ g_message ("Returning saved registration states: 1x: %d EVDO: %d",
+ priv->cdma_1x_reg_state, priv->evdo_reg_state);
+ callback (MM_MODEM_CDMA (modem), priv->cdma_1x_reg_state, priv->evdo_reg_state, NULL, user_data);
+ return;
+ }
+
+ /* Use secondary port if primary is connected */
+ port = priv->secondary;
+ }
+
+ info = mm_generic_cdma_query_reg_state_callback_info_new (MM_GENERIC_CDMA (modem), callback, user_data);
+ mm_serial_port_queue_command (port, "+CAD?", 3, get_analog_digital_done, info);
+}
+
+/*****************************************************************************/
+/* MMModemSimple interface */
+
+typedef enum {
+ SIMPLE_STATE_BEGIN = 0,
+ SIMPLE_STATE_ENABLE,
+ SIMPLE_STATE_REGISTER,
+ SIMPLE_STATE_CONNECT,
+ SIMPLE_STATE_DONE
+} SimpleState;
+
+static const char *
+simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error)
+{
+ GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties");
+ GValue *value;
+
+ value = (GValue *) g_hash_table_lookup (properties, name);
+ if (!value)
+ return NULL;
+
+ if (G_VALUE_HOLDS_STRING (value))
+ return g_value_get_string (value);
+
+ g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Invalid property type for '%s': %s (string expected)",
+ name, G_VALUE_TYPE_NAME (value));
+
+ return NULL;
+}
+
+static gboolean
+simple_reg_retry (gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (info->modem),
+ simple_reg_callback,
+ info);
+ return TRUE;
+}
+
+static void
+simple_reg_callback (MMModemCdma *modem,
+ MMModemCdmaRegistrationState cdma_1x_reg_state,
+ MMModemCdmaRegistrationState evdo_reg_state,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
+ gboolean no_service_error = FALSE;
+
+ if ( error
+ && (error->domain == MM_MOBILE_ERROR)
+ && (error->code == MM_MOBILE_ERROR_NO_NETWORK))
+ no_service_error = TRUE;
+
+ /* Fail immediately on anything but "no service" */
+ if (error && !no_service_error) {
+ simple_state_machine (MM_MODEM (modem), error, info);
+ return;
+ }
+
+ if ( no_service_error
+ || ( (cdma_1x_reg_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ && (evdo_reg_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN))) {
+ /* Not registered yet, queue up a retry */
+ priv->reg_tries++;
+ if (priv->reg_tries > 15) {
+ error = g_error_new_literal (MM_MOBILE_ERROR,
+ MM_MOBILE_ERROR_NO_NETWORK,
+ "No service");
+ simple_state_machine (MM_MODEM (modem), error, info);
+ g_error_free (error);
+ return;
+ }
+
+ /* otherwise, just try again in a bit */
+ if (!priv->reg_retry_id)
+ priv->reg_retry_id = g_timeout_add_seconds (4, simple_reg_retry, info);
+ } else {
+ /* Yay, at least one of 1x or EVDO is registered, we can proceed to dial */
+ simple_state_machine (MM_MODEM (modem), NULL, info);
+ }
+}
+
+static void
+reg_state_changed (MMModemCdma *self,
+ MMModemCdmaRegistrationState cdma_1x_new_state,
+ MMModemCdmaRegistrationState evdo_new_state,
+ gpointer user_data)
+{
+/* Disabled for now... changing the registration state from the
+ * subclass' query_registration_state handler also emits the registration
+ * state changed signal, which will call this function, and execute
+ * simple_state_machine() to advance to the next state. Then however
+ * query_registration_state will call its callback, which ends up in
+ * simple_reg_callback(), which calls simple_state_machine() too in
+ * the same mainloop iteration. Not good. So until that's sorted out
+ * we'll just have to poll registration state (every 4 seconds so its
+ * not that bad.
+ */
+#if 0
+ MMCallbackInfo *info = user_data;
+
+ /* If we're registered, we can proceed */
+ if ( (cdma_1x_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ || (evdo_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN))
+ simple_state_machine (MM_MODEM (modem), NULL, info);
+#endif
+}
+
+static SimpleState
+set_simple_state (MMCallbackInfo *info, SimpleState state)
+{
+ mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (state), NULL);
+ return state;
+}
+
+static void
+simple_state_machine (MMModem *modem, GError *error, gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
+ SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state"));
+ const char *str;
+ guint id;
+
+ if (error) {
+ info->error = g_error_copy (error);
+ goto out;
+ }
+
+ switch (state) {
+ case SIMPLE_STATE_BEGIN:
+ state = set_simple_state (info, SIMPLE_STATE_ENABLE);
+ mm_modem_enable (modem, simple_state_machine, info);
+ break;
+ case SIMPLE_STATE_ENABLE:
+ state = set_simple_state (info, SIMPLE_STATE_REGISTER);
+ mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (modem),
+ simple_reg_callback,
+ info);
+ id = g_signal_connect (modem,
+ MM_MODEM_CDMA_REGISTRATION_STATE_CHANGED,
+ G_CALLBACK (reg_state_changed),
+ info);
+ priv->reg_state_changed_id = id;
+ break;
+ case SIMPLE_STATE_REGISTER:
+ registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0);
+ state = set_simple_state (info, SIMPLE_STATE_CONNECT);
+ mm_modem_set_state (modem, MM_MODEM_STATE_REGISTERED, MM_MODEM_STATE_REASON_NONE);
+
+ str = simple_get_string_property (info, "number", &info->error);
+ mm_modem_connect (modem, str, simple_state_machine, info);
+ break;
+ case SIMPLE_STATE_CONNECT:
+ state = set_simple_state (info, SIMPLE_STATE_DONE);
+ break;
+ case SIMPLE_STATE_DONE:
+ break;
+ }
+
+ out:
+ if (info->error || state == SIMPLE_STATE_DONE) {
+ registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0);
+ mm_callback_info_schedule (info);
+ }
+}
+
+static void
+simple_connect (MMModemSimple *simple,
+ GHashTable *properties,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericCdma *self = MM_GENERIC_CDMA (simple);
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+ MMCallbackInfo *info;
+ GError *error = NULL;
+
+ if (priv->simple_connect_info) {
+ error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
+ "Connection is already in progress");
+ callback (MM_MODEM (simple), error, user_data);
+ g_error_free (error);
+ return;
+ }
+
+ info = mm_callback_info_new (MM_MODEM (simple), callback, user_data);
+ priv->simple_connect_info = info;
+ mm_callback_info_set_data (info, "simple-connect-properties",
+ g_hash_table_ref (properties),
+ (GDestroyNotify) g_hash_table_unref);
+
+ /* At least number must be present */
+ if (!simple_get_string_property (info, "number", &error)) {
+ if (!error)
+ error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Missing number property");
+ }
+
+ simple_state_machine (MM_MODEM (simple), error, info);
+}
+
+static void
+simple_free_gvalue (gpointer data)
+{
+ g_value_unset ((GValue *) data);
+ g_slice_free (GValue, data);
+}
+
+static GValue *
+simple_uint_value (guint32 i)
+{
+ GValue *val;
+
+ val = g_slice_new0 (GValue);
+ g_value_init (val, G_TYPE_UINT);
+ g_value_set_uint (val, i);
+
+ return val;
+}
+
+static void
+simple_status_got_signal_quality (MMModem *modem,
+ guint32 result,
+ GError *error,
+ gpointer user_data)
+{
+ if (error)
+ g_warning ("Error getting signal quality: %s", error->message);
+ else
+ g_hash_table_insert ((GHashTable *) user_data, "signal_quality", simple_uint_value (result));
+}
+
+static void
+simple_get_status_invoke (MMCallbackInfo *info)
+{
+ MMModemSimpleGetStatusFn callback = (MMModemSimpleGetStatusFn) info->callback;
+
+ callback (MM_MODEM_SIMPLE (info->modem),
+ (GHashTable *) mm_callback_info_get_data (info, "simple-get-status"),
+ info->error, info->user_data);
+}
+
+static void
+simple_get_status (MMModemSimple *simple,
+ MMModemSimpleGetStatusFn callback,
+ gpointer user_data)
+{
+ GHashTable *properties;
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new_full (MM_MODEM (simple),
+ simple_get_status_invoke,
+ G_CALLBACK (callback),
+ user_data);
+
+ properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, simple_free_gvalue);
+ mm_callback_info_set_data (info, "simple-get-status", properties, (GDestroyNotify) g_hash_table_unref);
+ mm_modem_cdma_get_signal_quality (MM_MODEM_CDMA (simple), simple_status_got_signal_quality, properties);
+}
+
+/*****************************************************************************/
+
+static void
+modem_valid_changed (MMGenericCdma *self, GParamSpec *pspec, gpointer user_data)
+{
+ /* Be paranoid about tearing down any pending registration */
+ if (!mm_modem_get_valid (MM_MODEM (self)))
+ registration_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL);
+}
+
+/*****************************************************************************/
+
+static void
+modem_init (MMModem *modem_class)
+{
+ modem_class->owns_port = owns_port;
+ modem_class->grab_port = grab_port;
+ modem_class->release_port = release_port;
+ modem_class->enable = enable;
+ modem_class->disable = disable;
+ modem_class->connect = connect;
+ modem_class->disconnect = disconnect;
+ modem_class->get_info = get_card_info;
+}
+
+static void
+modem_cdma_init (MMModemCdma *cdma_class)
+{
+ cdma_class->get_signal_quality = get_signal_quality;
+ cdma_class->get_esn = get_esn;
+ cdma_class->get_serving_system = get_serving_system;
+ cdma_class->get_registration_state = get_registration_state;
+}
+
+static void
+modem_simple_init (MMModemSimple *class)
+{
+ class->connect = simple_connect;
+ class->get_status = simple_get_status;
+}
+
+static GObject*
+constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+
+ object = G_OBJECT_CLASS (mm_generic_cdma_parent_class)->constructor (type,
+ n_construct_params,
+ construct_params);
+ if (object) {
+ g_signal_connect (object, "notify::" MM_MODEM_VALID,
+ G_CALLBACK (modem_valid_changed), NULL);
+ }
+
+ return object;
+}
+
+static void
+mm_generic_cdma_init (MMGenericCdma *self)
+{
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case MM_MODEM_PROP_TYPE:
+ break;
+ case PROP_EVDO_REV0:
+ priv->evdo_rev0 = g_value_get_boolean (value);
+ break;
+ case PROP_EVDO_REVA:
+ priv->evdo_revA = g_value_get_boolean (value);
+ break;
+ case PROP_REG_TRY_CSS:
+ priv->reg_try_css = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case MM_MODEM_PROP_DATA_DEVICE:
+ if (priv->data)
+ g_value_set_string (value, mm_port_get_device (priv->data));
+ else
+ g_value_set_string (value, NULL);
+ break;
+ case MM_MODEM_PROP_TYPE:
+ g_value_set_uint (value, MM_MODEM_TYPE_CDMA);
+ break;
+ case PROP_EVDO_REV0:
+ g_value_set_boolean (value, priv->evdo_rev0);
+ break;
+ case PROP_EVDO_REVA:
+ g_value_set_boolean (value, priv->evdo_revA);
+ break;
+ case PROP_REG_TRY_CSS:
+ g_value_set_boolean (value, priv->reg_try_css);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ registration_cleanup (MM_GENERIC_CDMA (object), MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL);
+
+ G_OBJECT_CLASS (mm_generic_cdma_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ G_OBJECT_CLASS (mm_generic_cdma_parent_class)->finalize (object);
+}
+
+static void
+mm_generic_cdma_class_init (MMGenericCdmaClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ mm_generic_cdma_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (object_class, sizeof (MMGenericCdmaPrivate));
+
+ /* Virtual methods */
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+ object_class->constructor = constructor;
+
+ /* Properties */
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_DATA_DEVICE,
+ MM_MODEM_DATA_DEVICE);
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_TYPE,
+ MM_MODEM_TYPE);
+
+ g_object_class_install_property (object_class, PROP_EVDO_REV0,
+ g_param_spec_boolean (MM_GENERIC_CDMA_EVDO_REV0,
+ "EVDO rev0",
+ "Supports EVDO rev0",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_EVDO_REVA,
+ g_param_spec_boolean (MM_GENERIC_CDMA_EVDO_REVA,
+ "EVDO revA",
+ "Supports EVDO revA",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_REG_TRY_CSS,
+ g_param_spec_boolean (MM_GENERIC_CDMA_REGISTRATION_TRY_CSS,
+ "RegistrationTryCss",
+ "Use Serving System response when checking modem"
+ " registration state.",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
diff --git a/src/mm-generic-cdma.h b/src/mm-generic-cdma.h
new file mode 100644
index 0000000..5b4a0b6
--- /dev/null
+++ b/src/mm-generic-cdma.h
@@ -0,0 +1,111 @@
+/* -*- 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.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_GENERIC_CDMA_H
+#define MM_GENERIC_CDMA_H
+
+#include "mm-modem.h"
+#include "mm-modem-base.h"
+#include "mm-modem-cdma.h"
+#include "mm-serial-port.h"
+#include "mm-callback-info.h"
+
+#define MM_TYPE_GENERIC_CDMA (mm_generic_cdma_get_type ())
+#define MM_GENERIC_CDMA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_GENERIC_CDMA, MMGenericCdma))
+#define MM_GENERIC_CDMA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_GENERIC_CDMA, MMGenericCdmaClass))
+#define MM_IS_GENERIC_CDMA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_GENERIC_CDMA))
+#define MM_IS_GENERIC_CDMA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_GENERIC_CDMA))
+#define MM_GENERIC_CDMA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_GENERIC_CDMA, MMGenericCdmaClass))
+
+#define MM_GENERIC_CDMA_EVDO_REV0 "evdo-rev0"
+#define MM_GENERIC_CDMA_EVDO_REVA "evdo-revA"
+
+#define MM_GENERIC_CDMA_REGISTRATION_TRY_CSS "registration-try-css"
+
+typedef struct {
+ MMModemBase parent;
+} MMGenericCdma;
+
+typedef struct {
+ MMModemBaseClass parent;
+
+ void (*query_registration_state) (MMGenericCdma *self,
+ MMModemCdmaRegistrationStateFn callback,
+ gpointer user_data);
+
+ /* Called after generic enable operations, but before the modem has entered
+ * the ENABLED state.
+ */
+ void (*post_enable) (MMGenericCdma *self,
+ MMModemFn callback,
+ gpointer user_data);
+
+ /* Called after generic disable operations, but before the modem has entered
+ * the DISABLED state.
+ */
+ void (*post_disable) (MMGenericCdma *self,
+ MMModemFn callback,
+ gpointer user_data);
+} MMGenericCdmaClass;
+
+GType mm_generic_cdma_get_type (void);
+
+MMModem *mm_generic_cdma_new (const char *device,
+ const char *driver,
+ const char *plugin,
+ gboolean evdo_rev0,
+ gboolean evdo_revA);
+
+/* Private, for subclasses */
+
+#define MM_GENERIC_CDMA_PREV_STATE_TAG "prev-state"
+
+MMPort * mm_generic_cdma_grab_port (MMGenericCdma *self,
+ const char *subsys,
+ const char *name,
+ MMPortType suggested_type,
+ gpointer user_data,
+ GError **error);
+
+MMSerialPort *mm_generic_cdma_get_port (MMGenericCdma *modem, MMPortType ptype);
+
+void mm_generic_cdma_update_cdma1x_quality (MMGenericCdma *self, guint32 quality);
+void mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality);
+
+/* For unsolicited 1x registration state changes */
+void mm_generic_cdma_set_1x_registration_state (MMGenericCdma *self,
+ MMModemCdmaRegistrationState new_state);
+
+/* For unsolicited EVDO registration state changes */
+void mm_generic_cdma_set_evdo_registration_state (MMGenericCdma *self,
+ MMModemCdmaRegistrationState new_state);
+
+MMModemCdmaRegistrationState mm_generic_cdma_1x_get_registration_state_sync (MMGenericCdma *self);
+
+MMModemCdmaRegistrationState mm_generic_cdma_evdo_get_registration_state_sync (MMGenericCdma *self);
+
+/* query_registration_state class function helpers */
+MMCallbackInfo *mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self,
+ MMModemCdmaRegistrationStateFn callback,
+ gpointer user_data);
+
+void mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info,
+ MMModemCdmaRegistrationState new_state);
+
+void mm_generic_cdma_query_reg_state_set_callback_evdo_state (MMCallbackInfo *info,
+ MMModemCdmaRegistrationState new_state);
+
+#endif /* MM_GENERIC_CDMA_H */
diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c
new file mode 100644
index 0000000..4954ca1
--- /dev/null
+++ b/src/mm-generic-gsm.c
@@ -0,0 +1,2220 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ * Copyright (C) 2009 Ericsson
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include "mm-generic-gsm.h"
+#include "mm-modem-gsm-card.h"
+#include "mm-modem-gsm-network.h"
+#include "mm-modem-gsm-sms.h"
+#include "mm-modem-simple.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+#include "mm-serial-parsers.h"
+#include "mm-modem-helpers.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_simple_init (MMModemSimple *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_SIMPLE, modem_simple_init))
+
+#define MM_GENERIC_GSM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_GENERIC_GSM, MMGenericGsmPrivate))
+
+typedef struct {
+ char *driver;
+ char *plugin;
+ char *device;
+
+ gboolean valid;
+
+ char *oper_code;
+ char *oper_name;
+ guint32 ip_method;
+ gboolean unsolicited_registration;
+
+ MMModemGsmNetworkRegStatus reg_status;
+ guint pending_reg_id;
+ MMCallbackInfo *pending_reg_info;
+
+ guint32 signal_quality;
+ guint32 cid;
+
+ MMSerialPort *primary;
+ MMSerialPort *secondary;
+ MMPort *data;
+} MMGenericGsmPrivate;
+
+static void get_registration_status (MMSerialPort *port, MMCallbackInfo *info);
+static void read_operator_code_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data);
+
+static void read_operator_name_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data);
+
+static void reg_state_changed (MMSerialPort *port,
+ GMatchInfo *match_info,
+ gpointer user_data);
+
+MMModem *
+mm_generic_gsm_new (const char *device,
+ const char *driver,
+ const char *plugin)
+{
+ g_return_val_if_fail (device != NULL, NULL);
+ g_return_val_if_fail (driver != NULL, NULL);
+ g_return_val_if_fail (plugin != NULL, NULL);
+
+ return MM_MODEM (g_object_new (MM_TYPE_GENERIC_GSM,
+ MM_MODEM_MASTER_DEVICE, device,
+ MM_MODEM_DRIVER, driver,
+ MM_MODEM_PLUGIN, plugin,
+ NULL));
+}
+
+void
+mm_generic_gsm_set_unsolicited_registration (MMGenericGsm *modem,
+ gboolean enabled)
+{
+ g_return_if_fail (MM_IS_GENERIC_GSM (modem));
+
+ MM_GENERIC_GSM_GET_PRIVATE (modem)->unsolicited_registration = enabled;
+}
+
+void
+mm_generic_gsm_set_cid (MMGenericGsm *modem, guint32 cid)
+{
+ g_return_if_fail (MM_IS_GENERIC_GSM (modem));
+
+ MM_GENERIC_GSM_GET_PRIVATE (modem)->cid = cid;
+}
+
+guint32
+mm_generic_gsm_get_cid (MMGenericGsm *modem)
+{
+ g_return_val_if_fail (MM_IS_GENERIC_GSM (modem), 0);
+
+ return MM_GENERIC_GSM_GET_PRIVATE (modem)->cid;
+}
+
+static void
+got_signal_quality (MMModem *modem,
+ guint32 result,
+ GError *error,
+ gpointer user_data)
+{
+}
+
+void
+mm_generic_gsm_set_reg_status (MMGenericGsm *modem,
+ MMModemGsmNetworkRegStatus status)
+{
+ MMGenericGsmPrivate *priv;
+
+ g_return_if_fail (MM_IS_GENERIC_GSM (modem));
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+
+ if (priv->reg_status != status) {
+ priv->reg_status = status;
+
+ g_debug ("Registration state changed: %d", status);
+
+ if (status == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME ||
+ status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) {
+ mm_serial_port_queue_command (priv->primary, "+COPS=3,2;+COPS?", 3, read_operator_code_done, modem);
+ mm_serial_port_queue_command (priv->primary, "+COPS=3,0;+COPS?", 3, read_operator_name_done, modem);
+ mm_modem_gsm_network_get_signal_quality (MM_MODEM_GSM_NETWORK (modem), got_signal_quality, NULL);
+ } else {
+ g_free (priv->oper_code);
+ g_free (priv->oper_name);
+ priv->oper_code = priv->oper_name = NULL;
+
+ mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (modem), priv->reg_status,
+ priv->oper_code, priv->oper_name);
+ }
+
+ mm_generic_gsm_update_enabled_state (modem, TRUE, MM_MODEM_STATE_REASON_NONE);
+ }
+}
+
+typedef struct {
+ const char *result;
+ guint code;
+} CPinResult;
+
+static void
+pin_check_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ gboolean parsed = FALSE;
+ static CPinResult results[] = {
+ { "SIM PIN", MM_MOBILE_ERROR_SIM_PIN },
+ { "SIM PUK", MM_MOBILE_ERROR_SIM_PUK },
+ { "PH-SIM PIN", MM_MOBILE_ERROR_PH_SIM_PIN },
+ { "PH-FSIM PIN", MM_MOBILE_ERROR_PH_FSIM_PIN },
+ { "PH-FSIM PUK", MM_MOBILE_ERROR_PH_FSIM_PUK },
+ { "SIM PIN2", MM_MOBILE_ERROR_SIM_PIN2 },
+ { "SIM PUK2", MM_MOBILE_ERROR_SIM_PUK2 },
+ { "PH-NET PIN", MM_MOBILE_ERROR_NETWORK_PIN },
+ { "PH-NET PUK", MM_MOBILE_ERROR_NETWORK_PUK },
+ { "PH-NETSUB PIN", MM_MOBILE_ERROR_NETWORK_SUBSET_PIN },
+ { "PH-NETSUB PUK", MM_MOBILE_ERROR_NETWORK_SUBSET_PUK },
+ { "PH-SP PIN", MM_MOBILE_ERROR_SERVICE_PIN },
+ { "PH-SP PUK", MM_MOBILE_ERROR_SERVICE_PUK },
+ { "PH-CORP PIN", MM_MOBILE_ERROR_CORP_PIN },
+ { "PH-CORP PUK", MM_MOBILE_ERROR_CORP_PUK },
+ { NULL, MM_MOBILE_ERROR_PHONE_FAILURE },
+ };
+
+ if (error)
+ info->error = g_error_copy (error);
+ else if (g_str_has_prefix (response->str, "+CPIN: ")) {
+ const char *str = response->str + 7;
+
+ if (g_str_has_prefix (str, "READY"))
+ parsed = TRUE;
+ else {
+ CPinResult *iter = &results[0];
+
+ /* Translate the error */
+ while (iter->result) {
+ if (g_str_has_prefix (str, iter->result)) {
+ info->error = mm_mobile_error_for_code (iter->code);
+ parsed = TRUE;
+ break;
+ }
+ iter++;
+ }
+ }
+ }
+
+ if (!info->error && !parsed) {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Could not parse PIN request response '%s'",
+ response->str);
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+void
+mm_generic_gsm_check_pin (MMGenericGsm *modem,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv;
+ MMCallbackInfo *info;
+
+ g_return_if_fail (MM_IS_GENERIC_GSM (modem));
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+ mm_serial_port_queue_command (priv->primary, "+CPIN?", 3, pin_check_done, info);
+}
+
+/*****************************************************************************/
+
+void
+mm_generic_gsm_update_enabled_state (MMGenericGsm *self,
+ gboolean stay_connected,
+ MMModemStateReason reason)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ /* While connected we don't want registration status changes to change
+ * the modem's state away from CONNECTED.
+ */
+ if (stay_connected && (mm_modem_get_state (MM_MODEM (self)) >= MM_MODEM_STATE_DISCONNECTING))
+ return;
+
+ switch (priv->reg_status) {
+ 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);
+ break;
+ case MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING:
+ mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_SEARCHING, reason);
+ break;
+ case MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE:
+ case MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED:
+ case MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN:
+ default:
+ mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_ENABLED, reason);
+ break;
+ }
+}
+
+static void
+check_valid (MMGenericGsm *self)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ gboolean new_valid = FALSE;
+
+ if (priv->primary && priv->data)
+ new_valid = TRUE;
+
+ mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid);
+}
+
+static gboolean
+owns_port (MMModem *modem, const char *subsys, const char *name)
+{
+ return !!mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name);
+}
+
+MMPort *
+mm_generic_gsm_grab_port (MMGenericGsm *self,
+ const char *subsys,
+ const char *name,
+ MMPortType ptype,
+ GError **error)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ MMPort *port = NULL;
+ GRegex *regex;
+
+ g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), FALSE);
+
+ port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype);
+ if (port && MM_IS_SERIAL_PORT (port)) {
+ mm_serial_port_set_response_parser (MM_SERIAL_PORT (port),
+ mm_serial_parser_v1_parse,
+ mm_serial_parser_v1_new (),
+ mm_serial_parser_v1_destroy);
+
+ regex = g_regex_new ("\\r\\n\\+CREG: (\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, reg_state_changed, self, NULL);
+ g_regex_unref (regex);
+
+ if (ptype == MM_PORT_TYPE_PRIMARY) {
+ priv->primary = MM_SERIAL_PORT (port);
+ if (!priv->data) {
+ priv->data = port;
+ g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE);
+ }
+ check_valid (self);
+ } else if (ptype == MM_PORT_TYPE_SECONDARY)
+ priv->secondary = MM_SERIAL_PORT (port);
+ } else {
+ /* Net device (if any) is the preferred data port */
+ if (!priv->data || MM_IS_SERIAL_PORT (priv->data)) {
+ priv->data = port;
+ g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE);
+ check_valid (self);
+ }
+ }
+
+ return port;
+}
+
+static gboolean
+grab_port (MMModem *modem,
+ const char *subsys,
+ const char *name,
+ MMPortType suggested_type,
+ gpointer user_data,
+ GError **error)
+{
+ MMGenericGsm *self = MM_GENERIC_GSM (modem);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ MMPortType ptype = MM_PORT_TYPE_IGNORED;
+
+ if (priv->primary)
+ g_return_val_if_fail (suggested_type != MM_PORT_TYPE_PRIMARY, FALSE);
+
+ if (!strcmp (subsys, "tty")) {
+ if (suggested_type != MM_PORT_TYPE_UNKNOWN)
+ ptype = suggested_type;
+ else {
+ if (!priv->primary)
+ ptype = MM_PORT_TYPE_PRIMARY;
+ else if (!priv->secondary)
+ ptype = MM_PORT_TYPE_SECONDARY;
+ }
+ }
+
+ return !!mm_generic_gsm_grab_port (self, subsys, name, ptype, error);
+}
+
+static void
+release_port (MMModem *modem, const char *subsys, const char *name)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMPort *port;
+
+ if (strcmp (subsys, "tty") && strcmp (subsys, "net"))
+ return;
+
+ port = mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name);
+ if (!port)
+ return;
+
+ if (port == MM_PORT (priv->primary)) {
+ mm_modem_base_remove_port (MM_MODEM_BASE (modem), port);
+ priv->primary = NULL;
+ }
+
+ if (port == priv->data) {
+ priv->data = NULL;
+ g_object_notify (G_OBJECT (modem), MM_MODEM_DATA_DEVICE);
+ }
+
+ if (port == MM_PORT (priv->secondary)) {
+ mm_modem_base_remove_port (MM_MODEM_BASE (modem), port);
+ priv->secondary = NULL;
+ }
+
+ check_valid (MM_GENERIC_GSM (modem));
+}
+
+void
+mm_generic_gsm_enable_complete (MMGenericGsm *modem,
+ GError *error,
+ MMCallbackInfo *info)
+{
+ g_return_if_fail (modem != NULL);
+ g_return_if_fail (MM_IS_GENERIC_GSM (modem));
+ g_return_if_fail (info != NULL);
+
+ if (error) {
+ mm_modem_set_state (MM_MODEM (modem),
+ MM_MODEM_STATE_DISABLED,
+ MM_MODEM_STATE_REASON_NONE);
+
+ info->error = g_error_copy (error);
+ } else {
+ /* Modem is enabled; update the state */
+ mm_generic_gsm_update_enabled_state (modem, FALSE, MM_MODEM_STATE_REASON_NONE);
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+enable_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ /* Ignore power-up command errors, not all devices actually support
+ * CFUN=1.
+ */
+ /* FIXME: instead of just ignoring errors, since we allow subclasses
+ * to override the power-on command, make a class function for powering
+ * on the phone and let the subclass decided whether it wants to handle
+ * errors or ignore them.
+ */
+
+ mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), NULL, info);
+}
+
+static void
+init_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ char *cmd = NULL;
+
+ if (error) {
+ mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info);
+ return;
+ }
+
+ /* Ensure echo is off after the init command; some modems ignore the
+ * E0 when it's in the same like as ATZ (Option GIO322).
+ */
+ mm_serial_port_queue_command (port, "E0 +CMEE=1", 2, NULL, NULL);
+
+ g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD_OPTIONAL, &cmd, NULL);
+ mm_serial_port_queue_command (port, cmd, 2, NULL, NULL);
+ g_free (cmd);
+
+ if (MM_GENERIC_GSM_GET_PRIVATE (info->modem)->unsolicited_registration)
+ mm_serial_port_queue_command (port, "+CREG=1", 5, NULL, NULL);
+ else
+ mm_serial_port_queue_command (port, "+CREG=0", 5, NULL, NULL);
+
+ g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_UP_CMD, &cmd, NULL);
+ if (cmd && strlen (cmd))
+ mm_serial_port_queue_command (port, cmd, 5, enable_done, user_data);
+ else
+ enable_done (port, NULL, NULL, user_data);
+ g_free (cmd);
+}
+
+static void
+enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+ char *cmd = NULL;
+
+ if (error) {
+ mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info);
+ return;
+ }
+
+ g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD, &cmd, NULL);
+ mm_serial_port_queue_command (port, cmd, 3, init_done, user_data);
+ g_free (cmd);
+}
+
+static void
+real_do_enable (MMGenericGsm *self, MMModemFn callback, gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new (MM_MODEM (self), callback, user_data);
+ mm_serial_port_flash (priv->primary, 100, enable_flash_done, info);
+}
+
+static void
+enable (MMModem *modem,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ GError *error = NULL;
+
+ /* First, reset the previously used CID */
+ mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0);
+
+ if (!mm_serial_port_open (priv->primary, &error)) {
+ MMCallbackInfo *info;
+
+ g_assert (error);
+ info = mm_callback_info_new (modem, callback, user_data);
+ info->error = error;
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ mm_modem_set_state (modem, MM_MODEM_STATE_ENABLING, MM_MODEM_STATE_REASON_NONE);
+
+ g_assert (MM_GENERIC_GSM_GET_CLASS (modem)->do_enable);
+ MM_GENERIC_GSM_GET_CLASS (modem)->do_enable (MM_GENERIC_GSM (modem), callback, user_data);
+}
+
+static void
+disable_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (!info->error) {
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ mm_serial_port_close (port);
+ mm_modem_set_state (MM_MODEM (info->modem),
+ MM_MODEM_STATE_DISABLED,
+ MM_MODEM_STATE_REASON_NONE);
+ priv->reg_status = MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN;
+ }
+ mm_callback_info_schedule (info);
+}
+
+static void
+disable_flash_done (MMSerialPort *port,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+ MMModemState prev_state;
+ char *cmd = NULL;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->error) {
+ if (info->modem) {
+ /* Reset old state since the operation failed */
+ prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_GSM_PREV_STATE_TAG));
+ mm_modem_set_state (MM_MODEM (info->modem),
+ prev_state,
+ MM_MODEM_STATE_REASON_NONE);
+ }
+
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_DOWN_CMD, &cmd, NULL);
+ if (cmd && strlen (cmd))
+ mm_serial_port_queue_command (port, cmd, 5, disable_done, user_data);
+ else
+ disable_done (port, NULL, NULL, user_data);
+ g_free (cmd);
+}
+
+static void
+disable (MMModem *modem,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ MMModemState state;
+
+ /* First, reset the previously used CID and clean up registration */
+ mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0);
+ mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem));
+
+ info = mm_callback_info_new (modem, callback, user_data);
+
+ /* Cache the previous state so we can reset it if the operation fails */
+ state = mm_modem_get_state (modem);
+ mm_callback_info_set_data (info,
+ MM_GENERIC_GSM_PREV_STATE_TAG,
+ GUINT_TO_POINTER (state),
+ NULL);
+
+ mm_modem_set_state (MM_MODEM (info->modem),
+ MM_MODEM_STATE_DISABLING,
+ MM_MODEM_STATE_REASON_NONE);
+
+ if (mm_port_get_connected (MM_PORT (priv->primary)))
+ mm_serial_port_flash (priv->primary, 1000, disable_flash_done, info);
+ else
+ disable_flash_done (priv->primary, NULL, info);
+}
+
+static void
+get_string_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ if (error)
+ info->error = g_error_copy (error);
+ else
+ mm_callback_info_set_result (info, g_strdup (response->str), g_free);
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+get_imei (MMModemGsmCard *modem,
+ MMModemStringFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
+ mm_serial_port_queue_command_cached (priv->primary, "+CGSN", 3, get_string_done, info);
+}
+
+static void
+get_imsi (MMModemGsmCard *modem,
+ MMModemStringFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
+ mm_serial_port_queue_command_cached (priv->primary, "+CIMI", 3, get_string_done, info);
+}
+
+static void
+card_info_invoke (MMCallbackInfo *info)
+{
+ MMModemInfoFn callback = (MMModemInfoFn) info->callback;
+
+ callback (info->modem,
+ (char *) mm_callback_info_get_data (info, "card-info-manufacturer"),
+ (char *) mm_callback_info_get_data (info, "card-info-model"),
+ (char *) mm_callback_info_get_data (info, "card-info-version"),
+ info->error, info->user_data);
+}
+
+#define GMI_RESP_TAG "+CGMI:"
+#define GMM_RESP_TAG "+CGMM:"
+#define GMR_RESP_TAG "+CGMR:"
+
+static const char *
+strip_tag (const char *str, const char *tag)
+{
+ /* Strip the response header, if any */
+ if (strncmp (str, tag, strlen (tag)) == 0)
+ str += strlen (tag);
+ while (*str && isspace (*str))
+ str++;
+ return str;
+}
+
+static void
+get_version_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ const char *resp = strip_tag (response->str, GMR_RESP_TAG);
+
+ if (!error)
+ mm_callback_info_set_data (info, "card-info-version", g_strdup (resp), g_free);
+ else if (!info->error)
+ info->error = g_error_copy (error);
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+get_model_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ const char *resp = strip_tag (response->str, GMM_RESP_TAG);
+
+ if (!error)
+ mm_callback_info_set_data (info, "card-info-model", g_strdup (resp), g_free);
+ else if (!info->error)
+ info->error = g_error_copy (error);
+}
+
+static void
+get_manufacturer_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ const char *resp = strip_tag (response->str, GMI_RESP_TAG);
+
+ if (!error)
+ mm_callback_info_set_data (info, "card-info-manufacturer", g_strdup (resp), g_free);
+ else
+ info->error = g_error_copy (error);
+}
+
+static void
+get_card_info (MMModem *modem,
+ MMModemInfoFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new_full (MM_MODEM (modem),
+ card_info_invoke,
+ G_CALLBACK (callback),
+ user_data);
+
+ mm_serial_port_queue_command_cached (priv->primary, "+CGMI", 3, get_manufacturer_done, info);
+ mm_serial_port_queue_command_cached (priv->primary, "+CGMM", 3, get_model_done, info);
+ mm_serial_port_queue_command_cached (priv->primary, "+CGMR", 3, get_version_done, info);
+}
+
+static void
+send_puk_done (MMSerialPort *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);
+}
+
+static void
+send_puk (MMModemGsmCard *modem,
+ const char *puk,
+ const char *pin,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ char *command;
+
+ info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+ command = g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin);
+ mm_serial_port_queue_command (priv->primary, command, 3, send_puk_done, info);
+ g_free (command);
+}
+
+static void
+send_pin_done (MMSerialPort *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);
+}
+
+static void
+send_pin (MMModemGsmCard *modem,
+ const char *pin,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ char *command;
+
+ info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+ command = g_strdup_printf ("+CPIN=\"%s\"", pin);
+ mm_serial_port_queue_command (priv->primary, command, 3, send_pin_done, info);
+ g_free (command);
+}
+
+static void
+enable_pin_done (MMSerialPort *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);
+}
+
+static void
+enable_pin (MMModemGsmCard *modem,
+ const char *pin,
+ gboolean enabled,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ char *command;
+
+ info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+ command = g_strdup_printf ("+CLCK=\"SC\",%d,\"%s\"", enabled ? 1 : 0, pin);
+ mm_serial_port_queue_command (priv->primary, command, 3, enable_pin_done, info);
+ g_free (command);
+}
+
+static void
+change_pin_done (MMSerialPort *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);
+}
+
+static void
+change_pin (MMModemGsmCard *modem,
+ const char *old_pin,
+ const char *new_pin,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ char *command;
+
+ info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+ command = g_strdup_printf ("+CPWD=\"SC\",\"%s\",\"%s\"", old_pin, new_pin);
+ mm_serial_port_queue_command (priv->primary, command, 3, change_pin_done, info);
+ g_free (command);
+}
+
+static char *
+parse_operator (const char *reply)
+{
+ char *operator = NULL;
+
+ if (reply && !strncmp (reply, "+COPS: ", 7)) {
+ /* Got valid reply */
+ GRegex *r;
+ GMatchInfo *match_info;
+
+ reply += 7;
+ r = g_regex_new ("(\\d),(\\d),\"(.+)\"", G_REGEX_UNGREEDY, 0, NULL);
+ if (!r)
+ return NULL;
+
+ g_regex_match (r, reply, 0, &match_info);
+ if (g_match_info_matches (match_info))
+ operator = g_match_info_fetch (match_info, 3);
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+ }
+
+ return operator;
+}
+
+static void
+read_operator_code_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (user_data);
+ char *oper;
+
+ if (error)
+ return;
+
+ oper = parse_operator (response->str);
+ if (!oper)
+ return;
+
+ g_free (priv->oper_code);
+ priv->oper_code = oper;
+}
+
+static void
+read_operator_name_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (user_data);
+ char *oper;
+
+ if (error)
+ return;
+
+ oper = parse_operator (response->str);
+ if (!oper)
+ return;
+
+ g_free (priv->oper_name);
+ priv->oper_name = oper;
+
+ mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (user_data),
+ priv->reg_status,
+ priv->oper_code,
+ priv->oper_name);
+}
+
+/* Registration */
+#define REG_STATUS_AGAIN_TAG "reg-status-again"
+
+void
+mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+
+ if (priv->pending_reg_id) {
+ /* Clear the registration timeout handler */
+ g_source_remove (priv->pending_reg_id);
+ priv->pending_reg_id = 0;
+ }
+
+ if (priv->pending_reg_info) {
+ /* Clear any ongoing registration status callback */
+ mm_callback_info_set_data (priv->pending_reg_info, REG_STATUS_AGAIN_TAG, NULL, NULL);
+
+ /* And schedule the callback */
+ mm_callback_info_schedule (priv->pending_reg_info);
+ priv->pending_reg_info = NULL;
+ }
+}
+
+static gboolean
+reg_status_updated (MMGenericGsm *self, int new_value, GError **error)
+{
+ MMModemGsmNetworkRegStatus status;
+ gboolean status_done = FALSE;
+
+ switch (new_value) {
+ case 0:
+ status = MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE;
+ break;
+ case 1:
+ status = MM_MODEM_GSM_NETWORK_REG_STATUS_HOME;
+ break;
+ case 2:
+ status = MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING;
+ break;
+ case 3:
+ status = MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED;
+ break;
+ case 4:
+ status = MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN;
+ break;
+ case 5:
+ status = MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING;
+ break;
+ default:
+ status = MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN;
+ break;
+ }
+
+ mm_generic_gsm_set_reg_status (self, status);
+
+ /* Registration has either completed successfully or completely failed */
+ switch (status) {
+ case MM_MODEM_GSM_NETWORK_REG_STATUS_HOME:
+ case MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING:
+ /* Successfully registered - stop registration */
+ status_done = TRUE;
+ break;
+ case MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED:
+ /* registration failed - stop registration */
+ if (error)
+ *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED);
+ status_done = TRUE;
+ break;
+ case MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING:
+ if (error)
+ *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_TIMEOUT);
+ break;
+ case MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE:
+ if (error)
+ *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NO_NETWORK);
+ break;
+ default:
+ if (error)
+ *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN);
+ break;
+ }
+ return status_done;
+}
+
+static void
+reg_state_changed (MMSerialPort *port,
+ GMatchInfo *match_info,
+ gpointer user_data)
+{
+ MMGenericGsm *self = MM_GENERIC_GSM (user_data);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ char *str;
+ gboolean done;
+
+ str = g_match_info_fetch (match_info, 1);
+ done = reg_status_updated (self, atoi (str), NULL);
+ g_free (str);
+
+ if (done) {
+ /* If registration is finished (either registered or failed) but the
+ * registration query hasn't completed yet, just remove the timeout and
+ * let the registration query complete.
+ */
+ if (priv->pending_reg_id) {
+ g_source_remove (priv->pending_reg_id);
+ priv->pending_reg_id = 0;
+ }
+ }
+}
+
+static gboolean
+reg_status_again (gpointer data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) data;
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ g_warn_if_fail (info == priv->pending_reg_info);
+
+ if (priv->pending_reg_info)
+ get_registration_status (priv->primary, info);
+
+ return FALSE;
+}
+
+static void
+reg_status_again_remove (gpointer data)
+{
+ guint id = GPOINTER_TO_UINT (data);
+
+ /* Technically the GSource ID can be 0, but in practice it won't be */
+ if (id > 0)
+ g_source_remove (id);
+}
+
+static void
+get_reg_status_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMGenericGsm *self = MM_GENERIC_GSM (info->modem);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ int reg_status = -1;
+ GRegex *r;
+ GMatchInfo *match_info;
+ char *tmp;
+ guint id;
+
+ g_warn_if_fail (info == priv->pending_reg_info);
+
+ if (error) {
+ info->error = g_error_copy (error);
+ goto reg_done;
+ }
+
+ r = g_regex_new ("\\+CREG:\\s*(\\d+),\\s*(\\d+)",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE,
+ 0, &info->error);
+ if (r) {
+ g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error);
+ if (g_match_info_matches (match_info)) {
+ /* Get reg status */
+ tmp = g_match_info_fetch (match_info, 2);
+ if (isdigit (tmp[0])) {
+ tmp[1] = '\0';
+ reg_status = atoi (tmp);
+ } else {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Unknown registration status '%s'",
+ tmp);
+ }
+ g_free (tmp);
+ } else {
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Could not parse the registration status response");
+ }
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+ }
+
+ if ( reg_status >= 0
+ && !reg_status_updated (self, reg_status, &info->error)
+ && priv->pending_reg_info) {
+ g_clear_error (&info->error);
+
+ /* Not registered yet; poll registration status again */
+ id = g_timeout_add_seconds (1, reg_status_again, info);
+ mm_callback_info_set_data (info, REG_STATUS_AGAIN_TAG,
+ GUINT_TO_POINTER (id),
+ reg_status_again_remove);
+ return;
+ }
+
+reg_done:
+ /* This will schedule the callback for us */
+ mm_generic_gsm_pending_registration_stop (self);
+}
+
+static void
+get_registration_status (MMSerialPort *port, MMCallbackInfo *info)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ g_warn_if_fail (info == priv->pending_reg_info);
+
+ mm_serial_port_queue_command (port, "+CREG?", 10, get_reg_status_done, info);
+}
+
+static void
+register_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ mm_callback_info_unref (info);
+
+ /* If the registration timed out (and thus pending_reg_info will be NULL)
+ * and the modem eventually got around to sending the response for the
+ * registration request then just ignore the response since the callback is
+ * already called.
+ */
+
+ if (priv->pending_reg_info) {
+ g_warn_if_fail (info == priv->pending_reg_info);
+
+ /* Ignore errors here, get the actual registration status */
+ get_registration_status (port, info);
+ }
+}
+
+static gboolean
+registration_timed_out (gpointer data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) data;
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ g_warn_if_fail (info == priv->pending_reg_info);
+
+ priv->reg_status = MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE;
+
+ info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_TIMEOUT);
+ mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (info->modem));
+
+ return FALSE;
+}
+
+static void
+do_register (MMModemGsmNetwork *modem,
+ const char *network_id,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ char *command;
+
+ /* Clear any previous registration */
+ mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem));
+
+ info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+
+ priv->pending_reg_id = g_timeout_add_seconds (60, registration_timed_out, info);
+ priv->pending_reg_info = info;
+
+ if (network_id)
+ command = g_strdup_printf ("+COPS=1,2,\"%s\"", network_id);
+ else
+ command = g_strdup ("+COPS=0,,");
+
+ /* Ref the callback info to ensure it stays alive for register_done() even
+ * if the timeout triggers and ends registration (which calls the callback
+ * and unrefs the callback info). Some devices (hso) will delay the
+ * registration response until the registration is done (and thus
+ * unsolicited registration responses will arrive before the +COPS is
+ * complete). Most other devices will return the +COPS response immediately
+ * and the unsolicited response (if any) at a later time.
+ *
+ * To handle both these cases, unsolicited registration responses will just
+ * remove the pending registration timeout but we let the +COPS command
+ * complete. For those devices that delay the +COPS response (hso) the
+ * callback will be called from register_done(). For those devices that
+ * return the +COPS response immediately, we'll poll the registration state
+ * and call the callback from get_reg_status_done() in response to the
+ * polled response. The registration timeout will only be triggered when
+ * the +COPS response is never received.
+ */
+ mm_callback_info_ref (info);
+ mm_serial_port_queue_command (priv->primary, command, 120, register_done, info);
+ g_free (command);
+}
+
+static void
+gsm_network_reg_info_invoke (MMCallbackInfo *info)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+ MMModemGsmNetworkRegInfoFn callback = (MMModemGsmNetworkRegInfoFn) info->callback;
+
+ callback (MM_MODEM_GSM_NETWORK (info->modem),
+ priv->reg_status,
+ priv->oper_code,
+ priv->oper_name,
+ info->error,
+ info->user_data);
+}
+
+static void
+get_registration_info (MMModemGsmNetwork *self,
+ MMModemGsmNetworkRegInfoFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new_full (MM_MODEM (self),
+ gsm_network_reg_info_invoke,
+ G_CALLBACK (callback),
+ user_data);
+
+ mm_callback_info_schedule (info);
+}
+
+void
+mm_generic_gsm_connect_complete (MMGenericGsm *modem,
+ GError *error,
+ MMCallbackInfo *info)
+{
+ MMGenericGsmPrivate *priv;
+
+ g_return_if_fail (modem != NULL);
+ g_return_if_fail (MM_IS_GENERIC_GSM (modem));
+ g_return_if_fail (info != NULL);
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+
+ if (error) {
+ mm_generic_gsm_update_enabled_state (modem, FALSE, MM_MODEM_STATE_REASON_NONE);
+ info->error = g_error_copy (error);
+ } else {
+ /* Modem is connected; update the state */
+ mm_port_set_connected (priv->data, TRUE);
+ mm_modem_set_state (MM_MODEM (modem),
+ MM_MODEM_STATE_CONNECTED,
+ MM_MODEM_STATE_REASON_NONE);
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+connect_report_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ GError *real_error;
+
+ /* If the CEER command was successful, copy that error reason into the
+ * callback's error. If not, use the original error.
+ */
+
+ /* Have to do this little dance since mm_generic_gsm_connect_complete()
+ * copies the provided error into the callback info.
+ */
+ real_error = info->error;
+ info->error = NULL;
+
+ if ( !error
+ && g_str_has_prefix (response->str, "+CEER: ")
+ && (strlen (response->str) > 7)) {
+ /* copy the connect failure reason into the error */
+ g_free (real_error->message);
+ real_error->message = g_strdup (response->str + 7); /* skip the "+CEER: " */
+ }
+
+ mm_generic_gsm_connect_complete (MM_GENERIC_GSM (info->modem), real_error, info);
+ g_error_free (real_error);
+}
+
+static void
+connect_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ if (error) {
+ info->error = g_error_copy (error);
+ /* Try to get more information why it failed */
+ priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+ mm_serial_port_queue_command (priv->primary, "+CEER", 3, connect_report_done, info);
+ } else
+ mm_generic_gsm_connect_complete (MM_GENERIC_GSM (info->modem), NULL, info);
+}
+
+static void
+connect (MMModem *modem,
+ const char *number,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ char *command;
+ guint32 cid = mm_generic_gsm_get_cid (MM_GENERIC_GSM (modem));
+
+ info = mm_callback_info_new (modem, callback, user_data);
+
+ mm_modem_set_state (modem, MM_MODEM_STATE_CONNECTING, MM_MODEM_STATE_REASON_NONE);
+
+ if (cid > 0) {
+ GString *str;
+
+ str = g_string_new ("D");
+ if (g_str_has_suffix (number, "#"))
+ str = g_string_append_len (str, number, strlen (number) - 1);
+ else
+ str = g_string_append (str, number);
+
+ g_string_append_printf (str, "***%d#", cid);
+ command = g_string_free (str, FALSE);
+ } else
+ command = g_strconcat ("DT", number, NULL);
+
+ mm_serial_port_queue_command (priv->primary, command, 60, connect_done, info);
+ g_free (command);
+}
+
+static void
+disconnect_flash_done (MMSerialPort *port,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMModemState prev_state;
+
+ info->error = mm_modem_check_removed (info->modem, error);
+ if (info->error) {
+ if (info->modem) {
+ /* Reset old state since the operation failed */
+ prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_GSM_PREV_STATE_TAG));
+ mm_modem_set_state (MM_MODEM (info->modem),
+ prev_state,
+ MM_MODEM_STATE_REASON_NONE);
+ }
+ } else {
+ MMGenericGsm *self = MM_GENERIC_GSM (info->modem);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ mm_port_set_connected (priv->data, FALSE);
+ mm_generic_gsm_update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE);
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+disconnect (MMModem *modem,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ MMModemState state;
+
+ /* First, reset the previously used CID */
+ mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0);
+
+ info = mm_callback_info_new (modem, callback, user_data);
+
+ /* Cache the previous state so we can reset it if the operation fails */
+ state = mm_modem_get_state (modem);
+ mm_callback_info_set_data (info,
+ MM_GENERIC_GSM_PREV_STATE_TAG,
+ GUINT_TO_POINTER (state),
+ NULL);
+
+ mm_modem_set_state (modem, MM_MODEM_STATE_DISCONNECTING, MM_MODEM_STATE_REASON_NONE);
+ mm_serial_port_flash (priv->primary, 1000, disconnect_flash_done, info);
+}
+
+static void
+gsm_network_scan_invoke (MMCallbackInfo *info)
+{
+ MMModemGsmNetworkScanFn callback = (MMModemGsmNetworkScanFn) info->callback;
+
+ callback (MM_MODEM_GSM_NETWORK (info->modem),
+ (GPtrArray *) mm_callback_info_get_data (info, "scan-results"),
+ info->error,
+ info->user_data);
+}
+
+static void
+scan_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ GPtrArray *results;
+
+ if (error)
+ info->error = g_error_copy (error);
+ else {
+ results = mm_gsm_parse_scan_response (response->str, &info->error);
+ if (results)
+ mm_callback_info_set_data (info, "scan-results", results, mm_gsm_destroy_scan_data);
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+scan (MMModemGsmNetwork *modem,
+ MMModemGsmNetworkScanFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new_full (MM_MODEM (modem),
+ gsm_network_scan_invoke,
+ G_CALLBACK (callback),
+ user_data);
+
+ mm_serial_port_queue_command (priv->primary, "+COPS=?", 120, scan_done, info);
+}
+
+/* SetApn */
+
+static void
+set_apn_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ if (error)
+ info->error = g_error_copy (error);
+ else
+ mm_generic_gsm_set_cid (MM_GENERIC_GSM (info->modem),
+ GPOINTER_TO_UINT (mm_callback_info_get_data (info, "cid")));
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+cid_range_read (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ guint32 cid = 0;
+
+ if (error)
+ info->error = g_error_copy (error);
+ else if (g_str_has_prefix (response->str, "+CGDCONT: ")) {
+ GRegex *r;
+ GMatchInfo *match_info;
+
+ r = g_regex_new ("\\+CGDCONT:\\s*\\((\\d+)-(\\d+)\\),\\(?\"(\\S+)\"",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, &info->error);
+ if (r) {
+ g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error);
+ while (cid == 0 && g_match_info_matches (match_info)) {
+ char *tmp;
+
+ tmp = g_match_info_fetch (match_info, 3);
+ if (!strcmp (tmp, "IP")) {
+ int max_cid;
+ int highest_cid = GPOINTER_TO_INT (mm_callback_info_get_data (info, "highest-cid"));
+
+ g_free (tmp);
+
+ tmp = g_match_info_fetch (match_info, 2);
+ max_cid = atoi (tmp);
+
+ if (highest_cid < max_cid)
+ cid = highest_cid + 1;
+ else
+ cid = highest_cid;
+ }
+
+ g_free (tmp);
+ g_match_info_next (match_info, NULL);
+ }
+
+ if (cid == 0)
+ /* Choose something */
+ cid = 1;
+ }
+ } else
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Could not parse the response");
+
+ if (info->error)
+ mm_callback_info_schedule (info);
+ else {
+ const char *apn = (const char *) mm_callback_info_get_data (info, "apn");
+ char *command;
+
+ mm_callback_info_set_data (info, "cid", GUINT_TO_POINTER (cid), NULL);
+
+ command = g_strdup_printf ("+CGDCONT=%d,\"IP\",\"%s\"", cid, apn);
+ mm_serial_port_queue_command (port, command, 3, set_apn_done, info);
+ g_free (command);
+ }
+}
+
+static void
+existing_apns_read (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ gboolean found = FALSE;
+
+ if (error)
+ info->error = g_error_copy (error);
+ else if (g_str_has_prefix (response->str, "+CGDCONT: ")) {
+ GRegex *r;
+ GMatchInfo *match_info;
+
+ r = g_regex_new ("\\+CGDCONT:\\s*(\\d+)\\s*,\"(\\S+)\",\"(\\S+)\",\"(\\S*)\"",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, &info->error);
+ if (r) {
+ const char *new_apn = (const char *) mm_callback_info_get_data (info, "apn");
+
+ g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error);
+ while (!found && g_match_info_matches (match_info)) {
+ char *cid;
+ char *pdp_type;
+ char *apn;
+ int num_cid;
+
+ cid = g_match_info_fetch (match_info, 1);
+ num_cid = atoi (cid);
+ pdp_type = g_match_info_fetch (match_info, 2);
+ apn = g_match_info_fetch (match_info, 3);
+
+ if (!strcmp (apn, new_apn)) {
+ mm_generic_gsm_set_cid (MM_GENERIC_GSM (info->modem), (guint32) num_cid);
+ found = TRUE;
+ }
+
+ if (!found && !strcmp (pdp_type, "IP")) {
+ int highest_cid;
+
+ highest_cid = GPOINTER_TO_INT (mm_callback_info_get_data (info, "highest-cid"));
+ if (num_cid > highest_cid)
+ mm_callback_info_set_data (info, "highest-cid", GINT_TO_POINTER (num_cid), NULL);
+ }
+
+ g_free (cid);
+ g_free (pdp_type);
+ g_free (apn);
+ g_match_info_next (match_info, NULL);
+ }
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+ }
+ } else if (strlen (response->str) == 0) {
+ /* No APNs configured, just don't set error */
+ } else
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Could not parse the response");
+
+ if (found || info->error)
+ mm_callback_info_schedule (info);
+ else
+ /* APN not configured on the card. Get the allowed CID range */
+ mm_serial_port_queue_command_cached (port, "+CGDCONT=?", 3, cid_range_read, info);
+}
+
+static void
+set_apn (MMModemGsmNetwork *modem,
+ const char *apn,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+ mm_callback_info_set_data (info, "apn", g_strdup (apn), g_free);
+
+ /* Start by searching if the APN is already in card */
+ mm_serial_port_queue_command (priv->primary, "+CGDCONT?", 3, existing_apns_read, info);
+}
+
+/* GetSignalQuality */
+
+static void
+get_signal_quality_done (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv;
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ char *reply = response->str;
+
+ if (error)
+ info->error = g_error_copy (error);
+ else if (!strncmp (reply, "+CSQ: ", 6)) {
+ /* Got valid reply */
+ int quality;
+ int ber;
+
+ reply += 6;
+
+ if (sscanf (reply, "%d, %d", &quality, &ber)) {
+ /* 99 means unknown */
+ if (quality == 99) {
+ info->error = g_error_new_literal (MM_MOBILE_ERROR,
+ MM_MOBILE_ERROR_NO_NETWORK,
+ "No service");
+ } else {
+ /* Normalize the quality */
+ quality = CLAMP (quality, 0, 31) * 100 / 31;
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+ priv->signal_quality = quality;
+ mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL);
+ }
+ } else
+ info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Could not parse signal quality results");
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+get_signal_quality (MMModemGsmNetwork *modem,
+ MMModemUIntFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ MMCallbackInfo *info;
+ gboolean connected;
+
+ connected = mm_port_get_connected (MM_PORT (priv->primary));
+ if (connected && !priv->secondary) {
+ g_message ("Returning saved signal quality %d", priv->signal_quality);
+ callback (MM_MODEM (modem), priv->signal_quality, NULL, user_data);
+ return;
+ }
+
+ info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data);
+ mm_serial_port_queue_command (connected ? priv->secondary : priv->primary, "+CSQ", 3, get_signal_quality_done, info);
+}
+
+/*****************************************************************************/
+/* MMModemGsmSms interface */
+
+static void
+sms_send_done (MMSerialPort *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);
+}
+
+static void
+sms_send (MMModemGsmSms *modem,
+ const char *number,
+ const char *text,
+ const char *smsc,
+ guint validity,
+ guint class,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv;
+ MMCallbackInfo *info;
+ char *command;
+ gboolean connected;
+ MMSerialPort *port = NULL;
+
+ info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+ connected = mm_port_get_connected (MM_PORT (priv->primary));
+ if (connected)
+ port = priv->secondary;
+ else
+ port = priv->primary;
+
+ if (!port) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED,
+ "Cannot send SMS while connected");
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ /* FIXME: use the PDU mode instead */
+ mm_serial_port_queue_command (port, "AT+CMGF=1", 3, NULL, NULL);
+
+ command = g_strdup_printf ("+CMGS=\"%s\"\r%s\x1a", number, text);
+ mm_serial_port_queue_command (port, command, 10, sms_send_done, info);
+ g_free (command);
+}
+
+MMSerialPort *
+mm_generic_gsm_get_port (MMGenericGsm *modem,
+ MMPortType ptype)
+{
+ g_return_val_if_fail (MM_IS_GENERIC_GSM (modem), NULL);
+ g_return_val_if_fail (ptype != MM_PORT_TYPE_UNKNOWN, NULL);
+
+ if (ptype == MM_PORT_TYPE_PRIMARY)
+ return MM_GENERIC_GSM_GET_PRIVATE (modem)->primary;
+ else if (ptype == MM_PORT_TYPE_SECONDARY)
+ return MM_GENERIC_GSM_GET_PRIVATE (modem)->secondary;
+
+ return NULL;
+}
+
+/*****************************************************************************/
+/* MMModemSimple interface */
+
+typedef enum {
+ SIMPLE_STATE_BEGIN = 0,
+ SIMPLE_STATE_ENABLE,
+ SIMPLE_STATE_CHECK_PIN,
+ SIMPLE_STATE_REGISTER,
+ SIMPLE_STATE_SET_APN,
+ SIMPLE_STATE_CONNECT,
+ SIMPLE_STATE_DONE
+} SimpleState;
+
+static const char *
+simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error)
+{
+ GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties");
+ GValue *value;
+
+ value = (GValue *) g_hash_table_lookup (properties, name);
+ if (!value)
+ return NULL;
+
+ if (G_VALUE_HOLDS_STRING (value))
+ return g_value_get_string (value);
+
+ g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Invalid property type for '%s': %s (string expected)",
+ name, G_VALUE_TYPE_NAME (value));
+
+ return NULL;
+}
+
+static void
+simple_state_machine (MMModem *modem, GError *error, gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ const char *str;
+ SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state"));
+ gboolean need_pin = FALSE;
+
+ if (error) {
+ if (g_error_matches (error, MM_MOBILE_ERROR, MM_MOBILE_ERROR_SIM_PIN)) {
+ need_pin = TRUE;
+ state = SIMPLE_STATE_CHECK_PIN;
+ } else {
+ info->error = g_error_copy (error);
+ goto out;
+ }
+ }
+
+ switch (state) {
+ case SIMPLE_STATE_BEGIN:
+ state = SIMPLE_STATE_ENABLE;
+ mm_modem_enable (modem, simple_state_machine, info);
+ break;
+ case SIMPLE_STATE_ENABLE:
+ state = SIMPLE_STATE_CHECK_PIN;
+ mm_generic_gsm_check_pin (MM_GENERIC_GSM (modem), simple_state_machine, info);
+ break;
+ case SIMPLE_STATE_CHECK_PIN:
+ if (need_pin) {
+ str = simple_get_string_property (info, "pin", &info->error);
+ if (str)
+ mm_modem_gsm_card_send_pin (MM_MODEM_GSM_CARD (modem), str, simple_state_machine, info);
+ else
+ info->error = g_error_copy (error);
+ } else {
+ str = simple_get_string_property (info, "network_id", &info->error);
+ state = SIMPLE_STATE_REGISTER;
+ if (!info->error)
+ mm_modem_gsm_network_register (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info);
+ }
+ break;
+ case SIMPLE_STATE_REGISTER:
+ str = simple_get_string_property (info, "apn", &info->error);
+ if (str) {
+ state = SIMPLE_STATE_SET_APN;
+ mm_modem_gsm_network_set_apn (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info);
+ break;
+ }
+ /* Fall through */
+ case SIMPLE_STATE_SET_APN:
+ str = simple_get_string_property (info, "number", &info->error);
+ state = SIMPLE_STATE_CONNECT;
+ mm_modem_connect (modem, str, simple_state_machine, info);
+ break;
+ case SIMPLE_STATE_CONNECT:
+ state = SIMPLE_STATE_DONE;
+ break;
+ case SIMPLE_STATE_DONE:
+ break;
+ }
+
+ out:
+ if (info->error || state == SIMPLE_STATE_DONE)
+ mm_callback_info_schedule (info);
+ else
+ mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (state), NULL);
+}
+
+static void
+simple_connect (MMModemSimple *simple,
+ GHashTable *properties,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new (MM_MODEM (simple), callback, user_data);
+ mm_callback_info_set_data (info, "simple-connect-properties",
+ g_hash_table_ref (properties),
+ (GDestroyNotify) g_hash_table_unref);
+
+ simple_state_machine (MM_MODEM (simple), NULL, info);
+}
+
+
+
+static void
+simple_free_gvalue (gpointer data)
+{
+ g_value_unset ((GValue *) data);
+ g_slice_free (GValue, data);
+}
+
+static GValue *
+simple_uint_value (guint32 i)
+{
+ GValue *val;
+
+ val = g_slice_new0 (GValue);
+ g_value_init (val, G_TYPE_UINT);
+ g_value_set_uint (val, i);
+
+ return val;
+}
+
+static GValue *
+simple_string_value (const char *str)
+{
+ GValue *val;
+
+ val = g_slice_new0 (GValue);
+ g_value_init (val, G_TYPE_STRING);
+ g_value_set_string (val, str);
+
+ return val;
+}
+
+static void
+simple_status_got_signal_quality (MMModem *modem,
+ guint32 result,
+ GError *error,
+ gpointer user_data)
+{
+ if (error)
+ g_warning ("Error getting signal quality: %s", error->message);
+ else
+ g_hash_table_insert ((GHashTable *) user_data, "signal_quality", simple_uint_value (result));
+}
+
+static void
+simple_status_got_band (MMModem *modem,
+ guint32 result,
+ GError *error,
+ gpointer user_data)
+{
+ /* Ignore band errors since there's no generic implementation for it */
+ if (!error)
+ g_hash_table_insert ((GHashTable *) user_data, "band", simple_uint_value (result));
+}
+
+static void
+simple_status_got_mode (MMModem *modem,
+ guint32 result,
+ GError *error,
+ gpointer user_data)
+{
+ /* Ignore network mode errors since there's no generic implementation for it */
+ if (!error)
+ g_hash_table_insert ((GHashTable *) user_data, "network_mode", simple_uint_value (result));
+}
+
+static void
+simple_status_got_reg_info (MMModemGsmNetwork *modem,
+ MMModemGsmNetworkRegStatus status,
+ const char *oper_code,
+ const char *oper_name,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ GHashTable *properties;
+
+ if (error)
+ info->error = g_error_copy (error);
+ else {
+ properties = (GHashTable *) mm_callback_info_get_data (info, "simple-get-status");
+
+ g_hash_table_insert (properties, "registration_status", simple_uint_value (status));
+ g_hash_table_insert (properties, "operator_code", simple_string_value (oper_code));
+ g_hash_table_insert (properties, "operator_name", simple_string_value (oper_name));
+ }
+
+ mm_callback_info_schedule (info);
+}
+
+static void
+simple_get_status_invoke (MMCallbackInfo *info)
+{
+ MMModemSimpleGetStatusFn callback = (MMModemSimpleGetStatusFn) info->callback;
+
+ callback (MM_MODEM_SIMPLE (info->modem),
+ (GHashTable *) mm_callback_info_get_data (info, "simple-get-status"),
+ info->error, info->user_data);
+}
+
+static void
+simple_get_status (MMModemSimple *simple,
+ MMModemSimpleGetStatusFn callback,
+ gpointer user_data)
+{
+ MMModemGsmNetwork *gsm;
+ GHashTable *properties;
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new_full (MM_MODEM (simple),
+ simple_get_status_invoke,
+ G_CALLBACK (callback),
+ user_data);
+
+ properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, simple_free_gvalue);
+ mm_callback_info_set_data (info, "simple-get-status", properties, (GDestroyNotify) g_hash_table_unref);
+
+ gsm = MM_MODEM_GSM_NETWORK (simple);
+ mm_modem_gsm_network_get_signal_quality (gsm, simple_status_got_signal_quality, properties);
+ mm_modem_gsm_network_get_band (gsm, simple_status_got_band, properties);
+ mm_modem_gsm_network_get_mode (gsm, simple_status_got_mode, properties);
+ mm_modem_gsm_network_get_registration_info (gsm, simple_status_got_reg_info, properties);
+}
+
+/*****************************************************************************/
+
+static void
+modem_init (MMModem *modem_class)
+{
+ modem_class->owns_port = owns_port;
+ modem_class->grab_port = grab_port;
+ modem_class->release_port = release_port;
+ modem_class->enable = enable;
+ modem_class->disable = disable;
+ modem_class->connect = connect;
+ modem_class->disconnect = disconnect;
+ modem_class->get_info = get_card_info;
+}
+
+static void
+modem_gsm_card_init (MMModemGsmCard *class)
+{
+ class->get_imei = get_imei;
+ class->get_imsi = get_imsi;
+ class->send_pin = send_pin;
+ class->send_puk = send_puk;
+ class->enable_pin = enable_pin;
+ class->change_pin = change_pin;
+}
+
+static void
+modem_gsm_network_init (MMModemGsmNetwork *class)
+{
+ class->do_register = do_register;
+ class->get_registration_info = get_registration_info;
+ class->set_apn = set_apn;
+ class->scan = scan;
+ class->get_signal_quality = get_signal_quality;
+}
+
+static void
+modem_gsm_sms_init (MMModemGsmSms *class)
+{
+ class->send = sms_send;
+}
+
+static void
+modem_simple_init (MMModemSimple *class)
+{
+ class->connect = simple_connect;
+ class->get_status = simple_get_status;
+}
+
+static void
+mm_generic_gsm_init (MMGenericGsm *self)
+{
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ switch (prop_id) {
+ case MM_MODEM_PROP_TYPE:
+ case MM_GENERIC_GSM_PROP_POWER_UP_CMD:
+ case MM_GENERIC_GSM_PROP_POWER_DOWN_CMD:
+ case MM_GENERIC_GSM_PROP_INIT_CMD:
+ case MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL:
+ case MM_GENERIC_GSM_PROP_SUPPORTED_BANDS:
+ case MM_GENERIC_GSM_PROP_SUPPORTED_MODES:
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case MM_MODEM_PROP_DATA_DEVICE:
+ if (priv->data)
+ g_value_set_string (value, mm_port_get_device (priv->data));
+ else
+ g_value_set_string (value, NULL);
+ break;
+ case MM_MODEM_PROP_TYPE:
+ g_value_set_uint (value, MM_MODEM_TYPE_GSM);
+ break;
+ case MM_GENERIC_GSM_PROP_POWER_UP_CMD:
+ g_value_set_string (value, "+CFUN=1");
+ break;
+ case MM_GENERIC_GSM_PROP_POWER_DOWN_CMD:
+ /* CFUN=0 is dangerous and often will shoot devices in the head (that's
+ * what it's supposed to do). So don't use CFUN=0 by default, but let
+ * specific plugins use it when they know it's safe to do so. For
+ * example, CFUN=0 will often make phones turn themselves off, but some
+ * dedicated devices (ex Sierra WWAN cards) will just turn off their
+ * radio but otherwise still work.
+ */
+ g_value_set_string (value, "");
+ break;
+ case MM_GENERIC_GSM_PROP_INIT_CMD:
+ g_value_set_string (value, "Z E0 V1 +CMEE=1");
+ break;
+ case MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL:
+ g_value_set_string (value, "X4 &C1");
+ break;
+ case MM_GENERIC_GSM_PROP_SUPPORTED_BANDS:
+ g_value_set_uint (value, 0);
+ break;
+ case MM_GENERIC_GSM_PROP_SUPPORTED_MODES:
+ g_value_set_uint (value, 0);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (object);
+
+ mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (object));
+
+ g_free (priv->oper_code);
+ g_free (priv->oper_name);
+
+ G_OBJECT_CLASS (mm_generic_gsm_parent_class)->finalize (object);
+}
+
+static void
+mm_generic_gsm_class_init (MMGenericGsmClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ mm_generic_gsm_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (object_class, sizeof (MMGenericGsmPrivate));
+
+ /* Virtual methods */
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->finalize = finalize;
+
+ klass->do_enable = real_do_enable;
+
+ /* Properties */
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_DATA_DEVICE,
+ MM_MODEM_DATA_DEVICE);
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_TYPE,
+ MM_MODEM_TYPE);
+
+ g_object_class_override_property (object_class,
+ MM_GENERIC_GSM_PROP_SUPPORTED_BANDS,
+ MM_MODEM_GSM_CARD_SUPPORTED_BANDS);
+
+ g_object_class_override_property (object_class,
+ MM_GENERIC_GSM_PROP_SUPPORTED_MODES,
+ MM_MODEM_GSM_CARD_SUPPORTED_MODES);
+
+ g_object_class_install_property
+ (object_class, MM_GENERIC_GSM_PROP_POWER_UP_CMD,
+ g_param_spec_string (MM_GENERIC_GSM_POWER_UP_CMD,
+ "PowerUpCommand",
+ "Power up command",
+ "+CFUN=1",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property
+ (object_class, MM_GENERIC_GSM_PROP_POWER_DOWN_CMD,
+ g_param_spec_string (MM_GENERIC_GSM_POWER_DOWN_CMD,
+ "PowerDownCommand",
+ "Power down command",
+ "+CFUN=0",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property
+ (object_class, MM_GENERIC_GSM_PROP_INIT_CMD,
+ g_param_spec_string (MM_GENERIC_GSM_INIT_CMD,
+ "InitCommand",
+ "Initialization command",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property
+ (object_class, MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL,
+ g_param_spec_string (MM_GENERIC_GSM_INIT_CMD_OPTIONAL,
+ "InitCommandOptional",
+ "Optional initialization command (errors ignored)",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
diff --git a/src/mm-generic-gsm.h b/src/mm-generic-gsm.h
new file mode 100644
index 0000000..de0b00b
--- /dev/null
+++ b/src/mm-generic-gsm.h
@@ -0,0 +1,129 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_GENERIC_GSM_H
+#define MM_GENERIC_GSM_H
+
+#include "mm-modem-gsm.h"
+#include "mm-modem-gsm-network.h"
+#include "mm-modem-base.h"
+#include "mm-serial-port.h"
+#include "mm-callback-info.h"
+
+#define MM_TYPE_GENERIC_GSM (mm_generic_gsm_get_type ())
+#define MM_GENERIC_GSM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_GENERIC_GSM, MMGenericGsm))
+#define MM_GENERIC_GSM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_GENERIC_GSM, MMGenericGsmClass))
+#define MM_IS_GENERIC_GSM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_GENERIC_GSM))
+#define MM_IS_GENERIC_GSM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_GENERIC_GSM))
+#define MM_GENERIC_GSM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_GENERIC_GSM, MMGenericGsmClass))
+
+#define MM_GENERIC_GSM_POWER_UP_CMD "power-up-cmd"
+#define MM_GENERIC_GSM_POWER_DOWN_CMD "power-down-cmd"
+#define MM_GENERIC_GSM_INIT_CMD "init-cmd"
+#define MM_GENERIC_GSM_INIT_CMD_OPTIONAL "init-cmd-optional"
+
+typedef enum {
+ MM_GENERIC_GSM_PROP_FIRST = 0x2000,
+
+ MM_GENERIC_GSM_PROP_POWER_UP_CMD,
+ MM_GENERIC_GSM_PROP_POWER_DOWN_CMD,
+ MM_GENERIC_GSM_PROP_INIT_CMD,
+ MM_GENERIC_GSM_PROP_SUPPORTED_BANDS,
+ MM_GENERIC_GSM_PROP_SUPPORTED_MODES,
+ MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL
+} MMGenericGsmProp;
+
+
+typedef struct {
+ MMModemBase parent;
+} MMGenericGsm;
+
+typedef struct {
+ MMModemBaseClass parent;
+
+ /* Called after opening the primary serial port and updating the modem's
+ * state to ENABLING, but before sending any commands to the device. Modems
+ * that need to perform custom initialization sequences or other setup should
+ * generally override this method instead of the MMModem interface's enable()
+ * method, unless the customization must happen *after* the generic init
+ * sequence has completed.
+ */
+ void (*do_enable) (MMGenericGsm *self, MMModemFn 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);
+
+/* Private, for subclasses */
+
+#define MM_GENERIC_GSM_PREV_STATE_TAG "prev-state"
+
+void mm_generic_gsm_set_unsolicited_registration (MMGenericGsm *modem,
+ gboolean enabled);
+
+void mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem);
+
+void mm_generic_gsm_set_cid (MMGenericGsm *modem,
+ guint32 cid);
+
+guint32 mm_generic_gsm_get_cid (MMGenericGsm *modem);
+void mm_generic_gsm_set_reg_status (MMGenericGsm *modem,
+ MMModemGsmNetworkRegStatus status);
+
+void mm_generic_gsm_check_pin (MMGenericGsm *modem,
+ MMModemFn callback,
+ gpointer user_data);
+
+MMSerialPort *mm_generic_gsm_get_port (MMGenericGsm *modem,
+ MMPortType ptype);
+
+MMPort *mm_generic_gsm_grab_port (MMGenericGsm *modem,
+ const char *subsys,
+ const char *name,
+ MMPortType ptype,
+ GError **error);
+
+/* stay_connected should be TRUE for unsolicited registration updates, otherwise
+ * the registration update will clear connected/connecting/disconnecting state
+ * which we don't want. stay_connected should be FALSE for other cases like
+ * updating the state after disconnecting, or after a connect error occurs.
+ */
+void mm_generic_gsm_update_enabled_state (MMGenericGsm *modem,
+ gboolean stay_connected,
+ MMModemStateReason reason);
+
+/* Called to complete the enable operation for custom enable() handling; if an
+ * error is passed in, it copies the error to the callback info. This function
+ * always schedules the callback info. It will also update the modem with the
+ * correct state for both failure and success of the enable operation.
+ */
+void mm_generic_gsm_enable_complete (MMGenericGsm *modem,
+ GError *error,
+ MMCallbackInfo *info);
+
+/* Called to complete the enable operation for custom connect() handling; if an
+ * error is passed in, it copies the error to the callback info. This function
+ * always schedules the callback info. It will also update the modem with the
+ * correct state for both failure and success of the connect operation.
+ */
+void mm_generic_gsm_connect_complete (MMGenericGsm *modem,
+ GError *error,
+ MMCallbackInfo *info);
+
+#endif /* MM_GENERIC_GSM_H */
diff --git a/src/mm-manager.c b/src/mm-manager.c
new file mode 100644
index 0000000..1a93170
--- /dev/null
+++ b/src/mm-manager.c
@@ -0,0 +1,714 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <gmodule.h>
+#define G_UDEV_API_IS_SUBJECT_TO_CHANGE
+#include <gudev/gudev.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include "mm-manager.h"
+#include "mm-errors.h"
+#include "mm-plugin.h"
+
+static gboolean impl_manager_enumerate_devices (MMManager *manager,
+ GPtrArray **devices,
+ GError **err);
+
+#include "mm-manager-glue.h"
+
+G_DEFINE_TYPE (MMManager, mm_manager, G_TYPE_OBJECT)
+
+enum {
+ DEVICE_ADDED,
+ DEVICE_REMOVED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+#define MM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MANAGER, MMManagerPrivate))
+
+typedef struct {
+ DBusGConnection *connection;
+ GUdevClient *udev;
+ GSList *plugins;
+ GHashTable *modems;
+
+ GHashTable *supports;
+} MMManagerPrivate;
+
+static MMPlugin *
+load_plugin (const char *path)
+{
+ MMPlugin *plugin = NULL;
+ GModule *module;
+ MMPluginCreateFunc plugin_create_func;
+ int *major_plugin_version, *minor_plugin_version;
+
+ module = g_module_open (path, G_MODULE_BIND_LAZY);
+ if (!module) {
+ g_warning ("Could not load plugin %s: %s", path, g_module_error ());
+ return NULL;
+ }
+
+ if (!g_module_symbol (module, "mm_plugin_major_version", (gpointer *) &major_plugin_version)) {
+ g_warning ("Could not load plugin %s: Missing major version info", path);
+ goto out;
+ }
+
+ if (*major_plugin_version != MM_PLUGIN_MAJOR_VERSION) {
+ g_warning ("Could not load plugin %s: Plugin major version %d, %d is required",
+ path, *major_plugin_version, MM_PLUGIN_MAJOR_VERSION);
+ goto out;
+ }
+
+ if (!g_module_symbol (module, "mm_plugin_minor_version", (gpointer *) &minor_plugin_version)) {
+ g_warning ("Could not load plugin %s: Missing minor version info", path);
+ goto out;
+ }
+
+ if (*minor_plugin_version != MM_PLUGIN_MINOR_VERSION) {
+ g_warning ("Could not load plugin %s: Plugin minor version %d, %d is required",
+ path, *minor_plugin_version, MM_PLUGIN_MINOR_VERSION);
+ goto out;
+ }
+
+ if (!g_module_symbol (module, "mm_plugin_create", (gpointer *) &plugin_create_func)) {
+ g_warning ("Could not load plugin %s: %s", path, g_module_error ());
+ goto out;
+ }
+
+ 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));
+ } else
+ g_warning ("Could not load plugin %s: initialization failed", path);
+
+ out:
+ if (!plugin)
+ g_module_close (module);
+
+ return plugin;
+}
+
+static void
+load_plugins (MMManager *manager)
+{
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
+ GDir *dir;
+ const char *fname;
+ MMPlugin *generic_plugin = NULL;
+
+ if (!g_module_supported ()) {
+ g_warning ("GModules are not supported on your platform!");
+ return;
+ }
+
+ dir = g_dir_open (PLUGINDIR, 0, NULL);
+ if (!dir) {
+ g_warning ("No plugins found");
+ return;
+ }
+
+ while ((fname = g_dir_read_name (dir)) != NULL) {
+ char *path;
+ MMPlugin *plugin;
+
+ if (!g_str_has_suffix (fname, G_MODULE_SUFFIX))
+ continue;
+
+ path = g_module_build_path (PLUGINDIR, fname);
+ plugin = load_plugin (path);
+ g_free (path);
+
+ if (plugin) {
+ if (!strcmp (mm_plugin_get_name (plugin), MM_PLUGIN_GENERIC_NAME))
+ generic_plugin = plugin;
+ else
+ priv->plugins = g_slist_append (priv->plugins, plugin);
+ }
+ }
+
+ /* Make sure the generic plugin is last */
+ if (generic_plugin)
+ priv->plugins = g_slist_append (priv->plugins, generic_plugin);
+
+ g_dir_close (dir);
+}
+
+MMManager *
+mm_manager_new (DBusGConnection *bus)
+{
+ MMManager *manager;
+
+ g_return_val_if_fail (bus != NULL, NULL);
+
+ manager = (MMManager *) g_object_new (MM_TYPE_MANAGER, NULL);
+ if (manager) {
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
+
+ priv->connection = dbus_g_connection_ref (bus);
+ dbus_g_connection_register_g_object (priv->connection,
+ MM_DBUS_PATH,
+ G_OBJECT (manager));
+ }
+
+ return manager;
+}
+
+static void
+remove_modem (MMManager *manager, MMModem *modem)
+{
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
+ char *device;
+
+ device = mm_modem_get_device (modem);
+ g_assert (device);
+ g_debug ("Removed modem %s", device);
+
+ g_signal_emit (manager, signals[DEVICE_REMOVED], 0, modem);
+ g_hash_table_remove (priv->modems, device);
+ g_free (device);
+}
+
+static void
+modem_valid (MMModem *modem, GParamSpec *pspec, gpointer user_data)
+{
+ MMManager *manager = MM_MANAGER (user_data);
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
+ static guint32 id = 0;
+ char *path, *device;
+
+ if (mm_modem_get_valid (modem)) {
+ 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);
+
+ device = mm_modem_get_device (modem);
+ g_assert (device);
+ g_debug ("Exported modem %s as %s", device, path);
+ g_free (device);
+
+ g_signal_emit (manager, signals[DEVICE_ADDED], 0, modem);
+ } else
+ remove_modem (manager, modem);
+}
+
+static void
+add_modem (MMManager *manager, MMModem *modem)
+{
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
+ char *device;
+ gboolean valid = FALSE;
+
+ device = mm_modem_get_device (modem);
+ g_assert (device);
+ if (!g_hash_table_lookup (priv->modems, device)) {
+ g_hash_table_insert (priv->modems, g_strdup (device), modem);
+ g_debug ("Added modem %s", device);
+ g_signal_connect (modem, "notify::valid", G_CALLBACK (modem_valid), manager);
+ g_object_get (modem, MM_MODEM_VALID, &valid, NULL);
+ if (valid)
+ modem_valid (modem, NULL, manager);
+ }
+ g_free (device);
+}
+
+static void
+enumerate_devices_cb (gpointer key, gpointer val, gpointer user_data)
+{
+ MMModem *modem = MM_MODEM (val);
+ GPtrArray **devices = (GPtrArray **) user_data;
+ const char *path;
+ gboolean valid = FALSE;
+
+ g_object_get (G_OBJECT (modem), MM_MODEM_VALID, &valid, NULL);
+ if (valid) {
+ path = g_object_get_data (G_OBJECT (modem), DBUS_PATH_TAG);
+ g_return_if_fail (path != NULL);
+ g_ptr_array_add (*devices, g_strdup (path));
+ }
+}
+
+static gboolean
+impl_manager_enumerate_devices (MMManager *manager,
+ GPtrArray **devices,
+ GError **err)
+{
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
+
+ *devices = g_ptr_array_sized_new (g_hash_table_size (priv->modems));
+ g_hash_table_foreach (priv->modems, enumerate_devices_cb, devices);
+
+ return TRUE;
+}
+
+static MMModem *
+find_modem_for_device (MMManager *manager, const char *device)
+{
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, priv->modems);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MMModem *modem = MM_MODEM (value);
+
+ if (!strcmp (device, mm_modem_get_device (modem)))
+ return modem;
+ }
+ return NULL;
+}
+
+
+static MMModem *
+find_modem_for_port (MMManager *manager, const char *subsys, const char *name)
+{
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, priv->modems);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MMModem *modem = MM_MODEM (value);
+
+ if (mm_modem_owns_port (modem, subsys, name))
+ return modem;
+ }
+ return NULL;
+}
+
+typedef struct {
+ MMManager *manager;
+ char *subsys;
+ char *name;
+ GSList *plugins;
+ GSList *cur_plugin;
+ guint defer_id;
+ guint done_id;
+
+ guint32 best_level;
+ MMPlugin *best_plugin;
+} SupportsInfo;
+
+static SupportsInfo *
+supports_info_new (MMManager *self, const char *subsys, const char *name)
+{
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (self);
+ SupportsInfo *info;
+
+ info = g_malloc0 (sizeof (SupportsInfo));
+ info->manager = self;
+ info->subsys = g_strdup (subsys);
+ info->name = g_strdup (name);
+ info->plugins = g_slist_copy (priv->plugins);
+ info->cur_plugin = info->plugins;
+ return info;
+}
+
+static void
+supports_info_free (SupportsInfo *info)
+{
+ /* Cancel any in-process operation on the first plugin */
+ if (info->cur_plugin)
+ mm_plugin_cancel_supports_port (MM_PLUGIN (info->cur_plugin->data), info->subsys, info->name);
+
+ if (info->defer_id)
+ g_source_remove (info->defer_id);
+
+ if (info->done_id)
+ g_source_remove (info->done_id);
+
+ g_free (info->subsys);
+ g_free (info->name);
+ g_slist_free (info->plugins);
+ memset (info, 0, sizeof (SupportsInfo));
+ g_free (info);
+}
+
+static char *
+get_key (const char *subsys, const char *name)
+{
+ return g_strdup_printf ("%s%s", subsys, name);
+}
+
+
+static void supports_callback (MMPlugin *plugin,
+ const char *subsys,
+ const char *name,
+ guint32 level,
+ gpointer user_data);
+
+static void try_supports_port (MMManager *manager,
+ MMPlugin *plugin,
+ const char *subsys,
+ const char *name,
+ SupportsInfo *info);
+
+static gboolean
+supports_defer_timeout (gpointer user_data)
+{
+ SupportsInfo *info = user_data;
+
+ g_debug ("(%s): re-checking support...", info->name);
+ try_supports_port (info->manager,
+ MM_PLUGIN (info->cur_plugin->data),
+ info->subsys,
+ info->name,
+ info);
+ return FALSE;
+}
+
+static void
+try_supports_port (MMManager *manager,
+ MMPlugin *plugin,
+ const char *subsys,
+ const char *name,
+ SupportsInfo *info)
+{
+ MMPluginSupportsResult result;
+
+ result = mm_plugin_supports_port (plugin, subsys, name, supports_callback, info);
+
+ switch (result) {
+ case MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED:
+ /* If the plugin knows it doesn't support the modem, just call the
+ * callback and indicate 0 support.
+ */
+ supports_callback (plugin, subsys, name, 0, info);
+ break;
+ case MM_PLUGIN_SUPPORTS_PORT_DEFER:
+ g_debug ("(%s): (%s) deferring support check", mm_plugin_get_name (plugin), name);
+ if (info->defer_id)
+ g_source_remove (info->defer_id);
+
+ /* defer port detection for a bit as requested by the plugin */
+ info->defer_id = g_timeout_add (3000, supports_defer_timeout, info);
+ break;
+ case MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS:
+ default:
+ break;
+ }
+}
+
+static gboolean
+do_grab_port (gpointer user_data)
+{
+ SupportsInfo *info = user_data;
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (info->manager);
+ MMModem *modem;
+ GError *error = NULL;
+ char *key;
+ GSList *iter;
+
+ /* No more plugins to try */
+ if (info->best_plugin) {
+ /* Create the modem */
+ modem = mm_plugin_grab_port (info->best_plugin, info->subsys, info->name, &error);
+ if (modem) {
+ guint32 modem_type = MM_MODEM_TYPE_UNKNOWN;
+ const char *type_name = "UNKNOWN";
+
+ g_object_get (G_OBJECT (modem), MM_MODEM_TYPE, &modem_type, NULL);
+ if (modem_type == MM_MODEM_TYPE_GSM)
+ type_name = "GSM";
+ else if (modem_type == MM_MODEM_TYPE_CDMA)
+ type_name = "CDMA";
+
+ g_message ("(%s): %s modem %s claimed port %s",
+ mm_plugin_get_name (info->best_plugin),
+ type_name,
+ mm_modem_get_device (modem),
+ info->name);
+
+ add_modem (info->manager, modem);
+ } 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)");
+ }
+ }
+
+ /* Tell each plugin to clean up any outstanding supports task */
+ for (iter = info->plugins; iter; iter = g_slist_next (iter))
+ mm_plugin_cancel_supports_port (MM_PLUGIN (iter->data), info->subsys, info->name);
+ g_slist_free (info->plugins);
+ info->cur_plugin = info->plugins = NULL;
+
+ key = get_key (info->subsys, info->name);
+ g_hash_table_remove (priv->supports, key);
+ g_free (key);
+
+ return FALSE;
+}
+
+static void
+supports_callback (MMPlugin *plugin,
+ const char *subsys,
+ const char *name,
+ guint32 level,
+ gpointer user_data)
+{
+ SupportsInfo *info = user_data;
+ MMPlugin *next_plugin = NULL;
+
+ info->cur_plugin = info->cur_plugin->next;
+ if (info->cur_plugin)
+ next_plugin = MM_PLUGIN (info->cur_plugin->data);
+
+ /* Is this plugin's result better than any one we've tried before? */
+ if (level > info->best_level) {
+ info->best_level = level;
+ info->best_plugin = plugin;
+ }
+
+ /* Prevent the generic plugin from probing devices that are already supported
+ * by other plugins. For Huawei for example, secondary ports shouldn't
+ * be probed, but the generic plugin would happily do so if allowed to.
+ */
+ if ( next_plugin
+ && !strcmp (mm_plugin_get_name (next_plugin), MM_PLUGIN_GENERIC_NAME)
+ && info->best_plugin)
+ next_plugin = NULL;
+
+ if (next_plugin) {
+ /* Try the next plugin */
+ try_supports_port (info->manager, next_plugin, info->subsys, info->name, info);
+ } else {
+ /* All done; let the best modem grab the port */
+ info->done_id = g_idle_add (do_grab_port, info);
+ }
+}
+
+static void
+device_added (MMManager *manager, GUdevDevice *device)
+{
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
+ const char *subsys, *name;
+ SupportsInfo *info;
+ char *key;
+ gboolean found;
+
+ g_return_if_fail (device != NULL);
+
+ if (!g_slist_length (priv->plugins))
+ return;
+
+ subsys = g_udev_device_get_subsystem (device);
+ name = g_udev_device_get_name (device);
+
+ if (find_modem_for_port (manager, subsys, name))
+ return;
+
+ key = get_key (subsys, name);
+ found = !!g_hash_table_lookup (priv->supports, key);
+ if (found) {
+ g_free (key);
+ return;
+ }
+
+ info = supports_info_new (manager, subsys, name);
+ g_hash_table_insert (priv->supports, key, info);
+
+ try_supports_port (manager, MM_PLUGIN (info->cur_plugin->data), subsys, name, info);
+}
+
+static void
+device_removed (MMManager *manager, GUdevDevice *device)
+{
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
+ MMModem *modem;
+ const char *subsys, *name;
+ char *key;
+ SupportsInfo *info;
+
+ g_return_if_fail (device != NULL);
+
+ if (!g_slist_length (priv->plugins))
+ return;
+
+ subsys = g_udev_device_get_subsystem (device);
+ name = g_udev_device_get_name (device);
+
+ if (strcmp (subsys, "usb") != 0) {
+ /* find_modem_for_port handles tty and net removal */
+ modem = find_modem_for_port (manager, subsys, name);
+ if (modem) {
+ mm_modem_release_port (modem, subsys, name);
+ return;
+ }
+ } else {
+ /* This case is designed to handle the case where, at least with kernel 2.6.31, unplugging
+ * an in-use ttyACMx device results in udev generating remove events for the usb, but the
+ * ttyACMx device (subsystem tty) is not removed, since it was in-use. So if we have not
+ * found a modem for the port (above), we're going to look here to see if we have a modem
+ * associated with the newly removed device. If so, we'll remove the modem, since the
+ * device has been removed. That way, if the device is reinserted later, we'll go through
+ * the process of exporting it.
+ */
+ 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);
+ remove_modem (manager, modem);
+ return;
+ }
+ }
+
+ /* Maybe a plugin is checking whether or not the port is supported */
+ key = get_key (subsys, name);
+ info = g_hash_table_lookup (priv->supports, key);
+
+ if (info) {
+ if (info->plugins)
+ mm_plugin_cancel_supports_port (MM_PLUGIN (info->plugins->data), subsys, name);
+ g_hash_table_remove (priv->supports, key);
+ }
+
+ g_free (key);
+}
+
+static void
+handle_uevent (GUdevClient *client,
+ const char *action,
+ GUdevDevice *device,
+ gpointer user_data)
+{
+ MMManager *self = MM_MANAGER (user_data);
+ const char *subsys;
+
+ g_return_if_fail (action != NULL);
+
+ /* A bit paranoid */
+ subsys = g_udev_device_get_subsystem (device);
+ g_return_if_fail (subsys != NULL);
+
+ g_return_if_fail (!strcmp (subsys, "tty") || !strcmp (subsys, "net") || !strcmp (subsys, "usb"));
+
+ /* We only care about tty/net devices when adding modem ports,
+ * but for remove, also handle usb parent device remove events
+ */
+ if ((!strcmp (action, "add") || !strcmp (action, "move")) && strcmp (subsys, "usb") !=0 )
+ device_added (self, device);
+ else if (!strcmp (action, "remove"))
+ device_removed (self, device);
+}
+
+void
+mm_manager_start (MMManager *manager)
+{
+ MMManagerPrivate *priv;
+ GList *devices, *iter;
+
+ g_return_if_fail (manager != NULL);
+ g_return_if_fail (MM_IS_MANAGER (manager));
+
+ priv = MM_MANAGER_GET_PRIVATE (manager);
+
+ devices = g_udev_client_query_by_subsystem (priv->udev, "tty");
+ for (iter = devices; iter; iter = g_list_next (iter))
+ device_added (manager, G_UDEV_DEVICE (iter->data));
+
+ devices = g_udev_client_query_by_subsystem (priv->udev, "net");
+ for (iter = devices; iter; iter = g_list_next (iter))
+ device_added (manager, G_UDEV_DEVICE (iter->data));
+}
+
+static void
+mm_manager_init (MMManager *manager)
+{
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager);
+ const char *subsys[4] = { "tty", "net", "usb", NULL };
+
+ priv->modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ load_plugins (manager);
+
+ priv->supports = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) supports_info_free);
+
+ priv->udev = g_udev_client_new (subsys);
+ g_assert (priv->udev);
+ g_signal_connect (priv->udev, "uevent", G_CALLBACK (handle_uevent), manager);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->supports);
+ g_hash_table_destroy (priv->modems);
+
+ g_slist_foreach (priv->plugins, (GFunc) g_object_unref, NULL);
+ g_slist_free (priv->plugins);
+
+ if (priv->udev)
+ g_object_unref (priv->udev);
+
+ if (priv->connection)
+ dbus_g_connection_unref (priv->connection);
+
+ G_OBJECT_CLASS (mm_manager_parent_class)->finalize (object);
+}
+
+static void
+mm_manager_class_init (MMManagerClass *manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (manager_class);
+
+ g_type_class_add_private (object_class, sizeof (MMManagerPrivate));
+
+ /* Virtual methods */
+ object_class->finalize = finalize;
+
+ /* Signals */
+ signals[DEVICE_ADDED] =
+ g_signal_new ("device-added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMManagerClass, device_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ G_TYPE_OBJECT);
+
+ signals[DEVICE_REMOVED] =
+ g_signal_new ("device-removed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMManagerClass, device_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ G_TYPE_OBJECT);
+
+ dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (manager_class),
+ &dbus_glib_mm_manager_object_info);
+
+ dbus_g_error_domain_register (MM_SERIAL_ERROR, "org.freedesktop.ModemManager.Modem", MM_TYPE_SERIAL_ERROR);
+ dbus_g_error_domain_register (MM_MODEM_ERROR, "org.freedesktop.ModemManager.Modem", MM_TYPE_MODEM_ERROR);
+ dbus_g_error_domain_register (MM_MODEM_CONNECT_ERROR, "org.freedesktop.ModemManager.Modem", MM_TYPE_MODEM_CONNECT_ERROR);
+ dbus_g_error_domain_register (MM_MOBILE_ERROR, "org.freedesktop.ModemManager.Modem.Gsm", MM_TYPE_MOBILE_ERROR);
+}
+
diff --git a/src/mm-manager.h b/src/mm-manager.h
new file mode 100644
index 0000000..5220b77
--- /dev/null
+++ b/src/mm-manager.h
@@ -0,0 +1,53 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_MANAGER_H
+#define MM_MANAGER_H
+
+#include <glib/gtypes.h>
+#include <glib-object.h>
+#include <dbus/dbus-glib.h>
+#include "mm-modem.h"
+
+#define MM_TYPE_MANAGER (mm_manager_get_type ())
+#define MM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MANAGER, MMManager))
+#define MM_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_MANAGER, MMManagerClass))
+#define MM_IS_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MANAGER))
+#define MM_IS_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), MM_TYPE_MANAGER))
+#define MM_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_MANAGER, MMManagerClass))
+
+#define MM_DBUS_SERVICE "org.freedesktop.ModemManager"
+#define MM_DBUS_PATH "/org/freedesktop/ModemManager"
+
+typedef struct {
+ GObject parent;
+} MMManager;
+
+typedef struct {
+ GObjectClass parent;
+
+ /* Signals */
+ void (*device_added) (MMManager *manager, MMModem *device);
+ void (*device_removed) (MMManager *manager, MMModem *device);
+} MMManagerClass;
+
+GType mm_manager_get_type (void);
+
+MMManager *mm_manager_new (DBusGConnection *bus);
+
+void mm_manager_start (MMManager *manager);
+
+#endif /* MM_MANAGER_H */
diff --git a/src/mm-modem-base.c b/src/mm-modem-base.c
new file mode 100644
index 0000000..3d82f8e
--- /dev/null
+++ b/src/mm-modem-base.c
@@ -0,0 +1,341 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "mm-modem-base.h"
+#include "mm-modem.h"
+#include "mm-serial-port.h"
+#include "mm-errors.h"
+#include "mm-options.h"
+#include "mm-properties-changed-signal.h"
+
+static void modem_init (MMModem *modem_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))
+
+#define MM_MODEM_BASE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_BASE, MMModemBasePrivate))
+
+typedef struct {
+ char *driver;
+ char *plugin;
+ char *device;
+ guint32 ip_method;
+ gboolean valid;
+ MMModemState state;
+
+ GHashTable *ports;
+} MMModemBasePrivate;
+
+
+static char *
+get_hash_key (const char *subsys, const char *name)
+{
+ return g_strdup_printf ("%s%s", subsys, name);
+}
+
+MMPort *
+mm_modem_base_get_port (MMModemBase *self,
+ const char *subsys,
+ const char *name)
+{
+ MMPort *port;
+ char *key;
+
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (subsys != NULL, NULL);
+
+ g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), NULL);
+
+ key = get_hash_key (subsys, name);
+ port = g_hash_table_lookup (MM_MODEM_BASE_GET_PRIVATE (self)->ports, key);
+ g_free (key);
+ return port;
+}
+
+static void
+find_primary (gpointer key, gpointer data, gpointer user_data)
+{
+ MMPort **found = user_data;
+ MMPort *port = MM_PORT (data);
+
+ if (!*found && (mm_port_get_port_type (port) == MM_PORT_TYPE_PRIMARY))
+ *found = port;
+}
+
+MMPort *
+mm_modem_base_add_port (MMModemBase *self,
+ const char *subsys,
+ const char *name,
+ MMPortType ptype)
+{
+ MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
+ MMPort *port = NULL;
+ char *key;
+
+ g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL);
+ g_return_val_if_fail (subsys != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (ptype != MM_PORT_TYPE_UNKNOWN, NULL);
+
+ g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), NULL);
+
+ key = get_hash_key (subsys, name);
+ port = g_hash_table_lookup (priv->ports, key);
+ g_free (key);
+ g_return_val_if_fail (port == NULL, NULL);
+
+ if (ptype == MM_PORT_TYPE_PRIMARY) {
+ g_hash_table_foreach (priv->ports, find_primary, &port);
+ g_return_val_if_fail (port == NULL, FALSE);
+ }
+
+ if (!strcmp (subsys, "tty"))
+ port = MM_PORT (mm_serial_port_new (name, ptype));
+ else if (!strcmp (subsys, "net")) {
+ port = MM_PORT (g_object_new (MM_TYPE_PORT,
+ MM_PORT_DEVICE, name,
+ MM_PORT_SUBSYS, MM_PORT_SUBSYS_NET,
+ MM_PORT_TYPE, ptype,
+ NULL));
+ }
+
+ if (!port)
+ return NULL;
+
+ key = get_hash_key (subsys, name);
+ g_hash_table_insert (priv->ports, key, port);
+ return port;
+}
+
+gboolean
+mm_modem_base_remove_port (MMModemBase *self, MMPort *port)
+{
+ 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);
+}
+
+void
+mm_modem_base_set_valid (MMModemBase *self, gboolean new_valid)
+{
+ MMModemBasePrivate *priv;
+
+ g_return_if_fail (MM_IS_MODEM_BASE (self));
+
+ priv = MM_MODEM_BASE_GET_PRIVATE (self);
+
+ if (priv->valid != new_valid) {
+ priv->valid = new_valid;
+
+ /* Modem starts off in disabled state, and jumps to disabled when
+ * it's no longer valid.
+ */
+ mm_modem_set_state (MM_MODEM (self),
+ MM_MODEM_STATE_DISABLED,
+ MM_MODEM_STATE_REASON_NONE);
+
+ g_object_notify (G_OBJECT (self), MM_MODEM_VALID);
+ }
+}
+
+gboolean
+mm_modem_base_get_valid (MMModemBase *self)
+{
+ g_return_val_if_fail (MM_IS_MODEM_BASE (self), FALSE);
+
+ return MM_MODEM_BASE_GET_PRIVATE (self)->valid;
+}
+
+/*****************************************************************************/
+
+static void
+mm_modem_base_init (MMModemBase *self)
+{
+ MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
+
+ priv->ports = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+
+ mm_properties_changed_signal_register_property (G_OBJECT (self),
+ MM_MODEM_ENABLED,
+ MM_MODEM_DBUS_INTERFACE);
+}
+
+static void
+modem_init (MMModem *modem_class)
+{
+}
+
+static gboolean
+is_enabled (MMModemState state)
+{
+ return (state >= MM_MODEM_STATE_ENABLED);
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (object);
+ gboolean old_enabled;
+
+ switch (prop_id) {
+ case MM_MODEM_PROP_STATE:
+ /* Ensure we update the 'enabled' property when the state changes */
+ old_enabled = is_enabled (priv->state);
+ priv->state = g_value_get_uint (value);
+ if (old_enabled != is_enabled (priv->state))
+ g_object_notify (object, MM_MODEM_ENABLED);
+ break;
+ case MM_MODEM_PROP_DRIVER:
+ /* Construct only */
+ priv->driver = g_value_dup_string (value);
+ break;
+ case MM_MODEM_PROP_PLUGIN:
+ /* Construct only */
+ priv->plugin = g_value_dup_string (value);
+ break;
+ case MM_MODEM_PROP_MASTER_DEVICE:
+ /* Construct only */
+ priv->device = g_value_dup_string (value);
+ break;
+ case MM_MODEM_PROP_IP_METHOD:
+ priv->ip_method = g_value_get_uint (value);
+ break;
+ case MM_MODEM_PROP_VALID:
+ case MM_MODEM_PROP_TYPE:
+ case MM_MODEM_PROP_ENABLED:
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case MM_MODEM_PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ case MM_MODEM_PROP_MASTER_DEVICE:
+ g_value_set_string (value, priv->device);
+ break;
+ case MM_MODEM_PROP_DATA_DEVICE:
+ g_value_set_string (value, NULL);
+ break;
+ case MM_MODEM_PROP_DRIVER:
+ g_value_set_string (value, priv->driver);
+ break;
+ case MM_MODEM_PROP_PLUGIN:
+ g_value_set_string (value, priv->plugin);
+ break;
+ case MM_MODEM_PROP_TYPE:
+ g_value_set_uint (value, MM_MODEM_TYPE_UNKNOWN);
+ break;
+ case MM_MODEM_PROP_IP_METHOD:
+ g_value_set_uint (value, priv->ip_method);
+ break;
+ case MM_MODEM_PROP_VALID:
+ g_value_set_boolean (value, priv->valid);
+ break;
+ case MM_MODEM_PROP_ENABLED:
+ g_value_set_boolean (value, is_enabled (priv->state));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ MMModemBase *self = MM_MODEM_BASE (object);
+ MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self);
+
+ g_hash_table_destroy (priv->ports);
+ g_free (priv->driver);
+ g_free (priv->plugin);
+ g_free (priv->device);
+
+ G_OBJECT_CLASS (mm_modem_base_parent_class)->finalize (object);
+}
+
+static void
+mm_modem_base_class_init (MMModemBaseClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMModemBasePrivate));
+
+ /* Virtual methods */
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->finalize = finalize;
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_STATE,
+ MM_MODEM_STATE);
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_MASTER_DEVICE,
+ MM_MODEM_MASTER_DEVICE);
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_DATA_DEVICE,
+ MM_MODEM_DATA_DEVICE);
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_DRIVER,
+ MM_MODEM_DRIVER);
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_PLUGIN,
+ MM_MODEM_PLUGIN);
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_TYPE,
+ MM_MODEM_TYPE);
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_IP_METHOD,
+ MM_MODEM_IP_METHOD);
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_VALID,
+ MM_MODEM_VALID);
+
+ g_object_class_override_property (object_class,
+ MM_MODEM_PROP_ENABLED,
+ MM_MODEM_ENABLED);
+
+ mm_properties_changed_signal_new (object_class);
+}
+
diff --git a/src/mm-modem-base.h b/src/mm-modem-base.h
new file mode 100644
index 0000000..9eb64ce
--- /dev/null
+++ b/src/mm-modem-base.h
@@ -0,0 +1,64 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_MODEM_BASE_H
+#define MM_MODEM_BASE_H
+
+#include <glib.h>
+#include <glib/gtypes.h>
+#include <glib-object.h>
+
+#include "mm-port.h"
+
+#define MM_TYPE_MODEM_BASE (mm_modem_base_get_type ())
+#define MM_MODEM_BASE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_BASE, MMModemBase))
+#define MM_MODEM_BASE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_MODEM_BASE, MMModemBaseClass))
+#define MM_IS_MODEM_BASE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_BASE))
+#define MM_IS_MODEM_BASE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_MODEM_BASE))
+#define MM_MODEM_BASE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_MODEM_BASE, MMModemBaseClass))
+
+typedef struct _MMModemBase MMModemBase;
+typedef struct _MMModemBaseClass MMModemBaseClass;
+
+struct _MMModemBase {
+ GObject parent;
+};
+
+struct _MMModemBaseClass {
+ GObjectClass parent;
+};
+
+GType mm_modem_base_get_type (void);
+
+MMPort *mm_modem_base_get_port (MMModemBase *self,
+ const char *subsys,
+ const char *name);
+
+MMPort *mm_modem_base_add_port (MMModemBase *self,
+ const char *subsys,
+ const char *name,
+ MMPortType ptype);
+
+gboolean mm_modem_base_remove_port (MMModemBase *self,
+ MMPort *port);
+
+void mm_modem_base_set_valid (MMModemBase *self,
+ gboolean valid);
+
+gboolean mm_modem_base_get_valid (MMModemBase *self);
+
+#endif /* MM_MODEM_BASE_H */
+
diff --git a/src/mm-modem-cdma.c b/src/mm-modem-cdma.c
new file mode 100644
index 0000000..112b93f
--- /dev/null
+++ b/src/mm-modem-cdma.c
@@ -0,0 +1,346 @@
+/* -*- 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.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <dbus/dbus-glib.h>
+#include "mm-modem-cdma.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+#include "mm-marshal.h"
+
+static void impl_modem_cdma_get_signal_quality (MMModemCdma *modem, DBusGMethodInvocation *context);
+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);
+
+#include "mm-modem-cdma-glue.h"
+
+enum {
+ SIGNAL_QUALITY,
+ REGISTRATION_STATE_CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/*****************************************************************************/
+
+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 (MMModemCdma *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
+uint_op_not_supported (MMModem *self,
+ MMModemUIntFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_uint_new (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
+uint_call_done (MMModem *modem, guint32 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
+serving_system_call_done (MMModemCdma *self,
+ guint32 class,
+ unsigned char band,
+ guint32 sid,
+ GError *error,
+ gpointer user_data)
+{
+ DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+
+ if (error)
+ dbus_g_method_return_error (context, error);
+ else {
+ GValueArray *array;
+ GValue value = { 0, };
+ char band_str[2] = { 0, 0 };
+
+ array = g_value_array_new (3);
+
+ /* Band Class */
+ g_value_init (&value, G_TYPE_UINT);
+ g_value_set_uint (&value, class);
+ g_value_array_append (array, &value);
+ g_value_unset (&value);
+
+ /* Band */
+ g_value_init (&value, G_TYPE_STRING);
+ band_str[0] = band;
+ g_value_set_string (&value, band_str);
+ g_value_array_append (array, &value);
+ g_value_unset (&value);
+
+ /* SID */
+ g_value_init (&value, G_TYPE_UINT);
+ g_value_set_uint (&value, sid);
+ g_value_array_append (array, &value);
+ g_value_unset (&value);
+
+ dbus_g_method_return (context, array);
+ }
+}
+
+static void
+serving_system_invoke (MMCallbackInfo *info)
+{
+ MMModemCdmaServingSystemFn callback = (MMModemCdmaServingSystemFn) info->callback;
+
+ callback (MM_MODEM_CDMA (info->modem), 0, 0, 0, info->error, info->user_data);
+}
+
+static void
+serving_system_call_not_supported (MMModemCdma *self,
+ MMModemCdmaServingSystemFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new_full (MM_MODEM (self), serving_system_invoke, G_CALLBACK (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_cdma_get_serving_system (MMModemCdma *self,
+ MMModemCdmaServingSystemFn 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)->get_serving_system)
+ MM_MODEM_CDMA_GET_INTERFACE (self)->get_serving_system (self, callback, user_data);
+ else
+ serving_system_call_not_supported (self, callback, user_data);
+}
+
+static void
+impl_modem_cdma_get_serving_system (MMModemCdma *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_cdma_get_serving_system (modem, serving_system_call_done, context);
+}
+
+void
+mm_modem_cdma_get_esn (MMModemCdma *self,
+ MMModemStringFn 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)->get_esn)
+ MM_MODEM_CDMA_GET_INTERFACE (self)->get_esn (self, callback, user_data);
+ else
+ str_call_not_supported (self, callback, user_data);
+}
+
+static void
+impl_modem_cdma_get_esn (MMModemCdma *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_cdma_get_esn (modem, str_call_done, context);
+}
+
+void
+mm_modem_cdma_get_signal_quality (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)->get_signal_quality)
+ MM_MODEM_CDMA_GET_INTERFACE (self)->get_signal_quality (self, callback, user_data);
+ else
+ uint_op_not_supported (MM_MODEM (self), callback, user_data);
+}
+
+static void
+impl_modem_cdma_get_signal_quality (MMModemCdma *modem, DBusGMethodInvocation *context)
+{
+ mm_modem_cdma_get_signal_quality (modem, uint_call_done, context);
+}
+
+void
+mm_modem_cdma_emit_signal_quality_changed (MMModemCdma *self, guint32 quality)
+{
+ g_return_if_fail (MM_IS_MODEM_CDMA (self));
+
+ g_signal_emit (self, signals[SIGNAL_QUALITY], 0, quality);
+}
+
+/*****************************************************************************/
+
+static void
+get_registration_state_call_done (MMModemCdma *self,
+ MMModemCdmaRegistrationState cdma_1x_reg_state,
+ MMModemCdmaRegistrationState evdo_reg_state,
+ 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, cdma_1x_reg_state, evdo_reg_state);
+}
+
+void
+mm_modem_cdma_get_registration_state (MMModemCdma *self,
+ MMModemCdmaRegistrationStateFn 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)->get_registration_state)
+ MM_MODEM_CDMA_GET_INTERFACE (self)->get_registration_state (self, callback, user_data);
+ else {
+ GError *error;
+
+ error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
+ "Operation not supported");
+
+ callback (self,
+ MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+ MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+ error,
+ user_data);
+
+ g_error_free (error);
+ }
+}
+
+static void
+impl_modem_cdma_get_registration_state (MMModemCdma *modem, DBusGMethodInvocation *context)
+{
+ mm_modem_cdma_get_registration_state (modem, get_registration_state_call_done, context);
+}
+
+void
+mm_modem_cdma_emit_registration_state_changed (MMModemCdma *self,
+ MMModemCdmaRegistrationState cdma_1x_new_state,
+ MMModemCdmaRegistrationState evdo_new_state)
+{
+ g_return_if_fail (MM_IS_MODEM_CDMA (self));
+
+ g_signal_emit (self, signals[REGISTRATION_STATE_CHANGED], 0, cdma_1x_new_state, evdo_new_state);
+}
+
+/*****************************************************************************/
+
+static void
+mm_modem_cdma_init (gpointer g_iface)
+{
+ GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+ static gboolean initialized = FALSE;
+
+ if (initialized)
+ return;
+
+ /* Signals */
+ signals[SIGNAL_QUALITY] =
+ g_signal_new ("signal-quality",
+ iface_type,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMModemCdma, signal_quality),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+
+ signals[REGISTRATION_STATE_CHANGED] =
+ g_signal_new ("registration-state-changed",
+ iface_type,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMModemCdma, registration_state_changed),
+ NULL, NULL,
+ mm_marshal_VOID__UINT_UINT,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
+
+ initialized = TRUE;
+}
+
+GType
+mm_modem_cdma_get_type (void)
+{
+ static GType modem_type = 0;
+
+ if (!G_UNLIKELY (modem_type)) {
+ const GTypeInfo modem_info = {
+ sizeof (MMModemCdma), /* class_size */
+ mm_modem_cdma_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL
+ };
+
+ modem_type = g_type_register_static (G_TYPE_INTERFACE,
+ "MMModemCdma",
+ &modem_info, 0);
+
+ g_type_interface_add_prerequisite (modem_type, MM_TYPE_MODEM);
+
+ dbus_g_object_type_install_info (modem_type, &dbus_glib_mm_modem_cdma_object_info);
+ }
+
+ return modem_type;
+}
diff --git a/src/mm-modem-cdma.h b/src/mm-modem-cdma.h
new file mode 100644
index 0000000..a7a626f
--- /dev/null
+++ b/src/mm-modem-cdma.h
@@ -0,0 +1,108 @@
+/* -*- 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.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_MODEM_CDMA_H
+#define MM_MODEM_CDMA_H
+
+#include <mm-modem.h>
+
+typedef enum {
+ MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN = 0,
+ MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED = 1,
+ MM_MODEM_CDMA_REGISTRATION_STATE_HOME = 2,
+ MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING = 3,
+
+ MM_MODEM_CDMA_REGISTRATION_STATE_LAST = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING
+} MMModemCdmaRegistrationState;
+
+#define MM_TYPE_MODEM_CDMA (mm_modem_cdma_get_type ())
+#define MM_MODEM_CDMA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_CDMA, MMModemCdma))
+#define MM_IS_MODEM_CDMA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_CDMA))
+#define MM_MODEM_CDMA_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_CDMA, MMModemCdma))
+
+#define MM_MODEM_CDMA_REGISTRATION_STATE_CHANGED "registration-state-changed"
+
+typedef struct _MMModemCdma MMModemCdma;
+
+typedef void (*MMModemCdmaServingSystemFn) (MMModemCdma *modem,
+ guint32 class,
+ unsigned char band,
+ guint32 sid,
+ GError *error,
+ gpointer user_data);
+
+typedef void (*MMModemCdmaRegistrationStateFn) (MMModemCdma *modem,
+ MMModemCdmaRegistrationState cdma_1x_reg_state,
+ MMModemCdmaRegistrationState evdo_reg_state,
+ GError *error,
+ gpointer user_data);
+
+struct _MMModemCdma {
+ GTypeInterface g_iface;
+
+ /* Methods */
+ void (*get_signal_quality) (MMModemCdma *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+ void (*get_esn) (MMModemCdma *self,
+ MMModemStringFn callback,
+ gpointer user_data);
+
+ void (*get_serving_system) (MMModemCdma *self,
+ MMModemCdmaServingSystemFn callback,
+ gpointer user_data);
+
+ void (*get_registration_state) (MMModemCdma *self,
+ MMModemCdmaRegistrationStateFn callback,
+ gpointer user_data);
+
+ /* Signals */
+ void (*signal_quality) (MMModemCdma *self,
+ guint32 quality);
+
+ void (*registration_state_changed) (MMModemCdma *self,
+ MMModemCdmaRegistrationState cdma_1x_new_state,
+ MMModemCdmaRegistrationState evdo_new_state);
+};
+
+GType mm_modem_cdma_get_type (void);
+
+void mm_modem_cdma_get_signal_quality (MMModemCdma *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+void mm_modem_cdma_get_esn (MMModemCdma *self,
+ MMModemStringFn callback,
+ gpointer user_data);
+
+void mm_modem_cdma_get_serving_system (MMModemCdma *self,
+ MMModemCdmaServingSystemFn callback,
+ gpointer user_data);
+
+void mm_modem_cdma_get_registration_state (MMModemCdma *self,
+ MMModemCdmaRegistrationStateFn callback,
+ gpointer user_data);
+
+/* Protected */
+
+void mm_modem_cdma_emit_signal_quality_changed (MMModemCdma *self, guint32 new_quality);
+
+void mm_modem_cdma_emit_registration_state_changed (MMModemCdma *self,
+ MMModemCdmaRegistrationState cdma_1x_new_state,
+ MMModemCdmaRegistrationState evdo_new_state);
+
+#endif /* MM_MODEM_CDMA_H */
diff --git a/src/mm-modem-gsm-card.c b/src/mm-modem-gsm-card.c
new file mode 100644
index 0000000..dea4590
--- /dev/null
+++ b/src/mm-modem-gsm-card.c
@@ -0,0 +1,312 @@
+/* -*- 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.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include <dbus/dbus-glib.h>
+
+#include "mm-modem-gsm-card.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+#include "mm-modem-gsm.h"
+
+static void impl_gsm_modem_get_imei (MMModemGsmCard *modem,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_get_imsi (MMModemGsmCard *modem,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_send_pin (MMModemGsmCard *modem,
+ const char *pin,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_send_puk (MMModemGsmCard *modem,
+ const char *puk,
+ const char *pin,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_enable_pin (MMModemGsmCard *modem,
+ const char *pin,
+ gboolean enabled,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_change_pin (MMModemGsmCard *modem,
+ const char *old_pin,
+ const char *new_pin,
+ DBusGMethodInvocation *context);
+
+#include "mm-modem-gsm-card-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 (MMModemGsmCard *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 (MMModemGsmCard *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_card_get_imei (MMModemGsmCard *self,
+ MMModemStringFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_CARD (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_imei)
+ MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_imei (self, callback, user_data);
+ else
+ str_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_card_get_imsi (MMModemGsmCard *self,
+ MMModemStringFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_CARD (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_imsi)
+ MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_imsi (self, callback, user_data);
+ else
+ str_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_card_send_puk (MMModemGsmCard *self,
+ const char *puk,
+ const char *pin,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_CARD (self));
+ g_return_if_fail (puk != NULL);
+ g_return_if_fail (pin != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->send_puk)
+ MM_MODEM_GSM_CARD_GET_INTERFACE (self)->send_puk (self, puk, pin, callback, user_data);
+ else
+ async_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_card_send_pin (MMModemGsmCard *self,
+ const char *pin,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_CARD (self));
+ g_return_if_fail (pin != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->send_pin)
+ MM_MODEM_GSM_CARD_GET_INTERFACE (self)->send_pin (self, pin, callback, user_data);
+ else
+ async_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_card_enable_pin (MMModemGsmCard *self,
+ const char *pin,
+ gboolean enabled,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_CARD (self));
+ g_return_if_fail (pin != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->enable_pin)
+ MM_MODEM_GSM_CARD_GET_INTERFACE (self)->enable_pin (self, pin, enabled, callback, user_data);
+ else
+ async_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_card_change_pin (MMModemGsmCard *self,
+ const char *old_pin,
+ const char *new_pin,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_CARD (self));
+ g_return_if_fail (old_pin != NULL);
+ g_return_if_fail (new_pin != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->change_pin)
+ MM_MODEM_GSM_CARD_GET_INTERFACE (self)->change_pin (self, old_pin, new_pin, callback, user_data);
+ else
+ async_call_not_supported (self, callback, user_data);
+}
+
+/*****************************************************************************/
+
+static void
+impl_gsm_modem_get_imei (MMModemGsmCard *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_card_get_imei (modem, str_call_done, context);
+}
+
+static void
+impl_gsm_modem_get_imsi (MMModemGsmCard *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_card_get_imsi (modem, str_call_done, context);
+}
+
+static void
+ impl_gsm_modem_send_puk (MMModemGsmCard *modem,
+ const char *puk,
+ const char *pin,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_card_send_puk (modem, puk, pin, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_send_pin (MMModemGsmCard *modem,
+ const char *pin,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_card_send_pin (modem, pin, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_enable_pin (MMModemGsmCard *modem,
+ const char *pin,
+ gboolean enabled,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_card_enable_pin (modem, pin, enabled, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_change_pin (MMModemGsmCard *modem,
+ const char *old_pin,
+ const char *new_pin,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_card_change_pin (modem, old_pin, new_pin, async_call_done, context);
+}
+
+/*****************************************************************************/
+
+static void
+mm_modem_gsm_card_init (gpointer g_iface)
+{
+ static gboolean initialized = FALSE;
+
+ if (G_LIKELY (initialized))
+ return;
+
+ initialized = TRUE;
+
+ 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",
+ MM_MODEM_GSM_BAND_UNKNOWN,
+ MM_MODEM_GSM_BAND_LAST,
+ MM_MODEM_GSM_BAND_UNKNOWN,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_uint (MM_MODEM_GSM_CARD_SUPPORTED_MODES,
+ "Supported Modes",
+ "Supported modes of the card (ex 2G preferred, 3G preferred, 2G only, etc",
+ MM_MODEM_GSM_MODE_UNKNOWN,
+ MM_MODEM_GSM_MODE_LAST,
+ MM_MODEM_GSM_MODE_UNKNOWN,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+GType
+mm_modem_gsm_card_get_type (void)
+{
+ static GType card_type = 0;
+
+ if (G_UNLIKELY (!card_type)) {
+ const GTypeInfo card_info = {
+ sizeof (MMModemGsmCard), /* class_size */
+ mm_modem_gsm_card_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL
+ };
+
+ card_type = g_type_register_static (G_TYPE_INTERFACE,
+ "MMModemGsmCard",
+ &card_info, 0);
+
+ g_type_interface_add_prerequisite (card_type, G_TYPE_OBJECT);
+ dbus_g_object_type_install_info (card_type, &dbus_glib_mm_modem_gsm_card_object_info);
+ }
+
+ return card_type;
+}
diff --git a/src/mm-modem-gsm-card.h b/src/mm-modem-gsm-card.h
new file mode 100644
index 0000000..4d690e6
--- /dev/null
+++ b/src/mm-modem-gsm-card.h
@@ -0,0 +1,101 @@
+/* -*- 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.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_MODEM_GSM_CARD_H
+#define MM_MODEM_GSM_CARD_H
+
+#include <mm-modem.h>
+
+#define MM_TYPE_MODEM_GSM_CARD (mm_modem_gsm_card_get_type ())
+#define MM_MODEM_GSM_CARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_GSM_CARD, MMModemGsmCard))
+#define MM_IS_MODEM_GSM_CARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_GSM_CARD))
+#define MM_MODEM_GSM_CARD_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_GSM_CARD, MMModemGsmCard))
+
+#define MM_MODEM_GSM_CARD_SUPPORTED_BANDS "supported-bands"
+#define MM_MODEM_GSM_CARD_SUPPORTED_MODES "supported-modes"
+
+typedef struct _MMModemGsmCard MMModemGsmCard;
+
+struct _MMModemGsmCard {
+ GTypeInterface g_iface;
+
+ /* Methods */
+ void (*get_imei) (MMModemGsmCard *self,
+ MMModemStringFn callback,
+ gpointer user_data);
+
+ void (*get_imsi) (MMModemGsmCard *self,
+ MMModemStringFn callback,
+ gpointer user_data);
+
+ void (*send_puk) (MMModemGsmCard *self,
+ const char *puk,
+ const char *pin,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*send_pin) (MMModemGsmCard *self,
+ const char *pin,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*enable_pin) (MMModemGsmCard *self,
+ const char *pin,
+ gboolean enabled,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*change_pin) (MMModemGsmCard *self,
+ const char *old_pin,
+ const char *new_pin,
+ MMModemFn callback,
+ gpointer user_data);
+};
+
+GType mm_modem_gsm_card_get_type (void);
+
+void mm_modem_gsm_card_get_imei (MMModemGsmCard *self,
+ MMModemStringFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_card_get_imsi (MMModemGsmCard *self,
+ MMModemStringFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_card_send_puk (MMModemGsmCard *self,
+ const char *puk,
+ const char *pin,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_card_send_pin (MMModemGsmCard *self,
+ const char *pin,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_card_enable_pin (MMModemGsmCard *self,
+ const char *pin,
+ gboolean enabled,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_card_change_pin (MMModemGsmCard *self,
+ const char *old_pin,
+ const char *new_pin,
+ MMModemFn callback,
+ gpointer user_data);
+
+#endif /* MM_MODEM_GSM_CARD_H */
diff --git a/src/mm-modem-gsm-network.c b/src/mm-modem-gsm-network.c
new file mode 100644
index 0000000..26ff422
--- /dev/null
+++ b/src/mm-modem-gsm-network.c
@@ -0,0 +1,569 @@
+/* -*- 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 <string.h>
+#include <dbus/dbus-glib.h>
+
+#include "mm-modem-gsm-network.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+#include "mm-marshal.h"
+
+static void impl_gsm_modem_register (MMModemGsmNetwork *modem,
+ const char *network_id,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_scan (MMModemGsmNetwork *modem,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_set_apn (MMModemGsmNetwork *modem,
+ const char *apn,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_get_signal_quality (MMModemGsmNetwork *modem,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_set_band (MMModemGsmNetwork *modem,
+ MMModemGsmBand band,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_get_band (MMModemGsmNetwork *modem,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_set_network_mode (MMModemGsmNetwork *modem,
+ MMModemGsmMode mode,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_get_network_mode (MMModemGsmNetwork *modem,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_get_reg_info (MMModemGsmNetwork *modem,
+ DBusGMethodInvocation *context);
+
+#include "mm-modem-gsm-network-glue.h"
+
+/*****************************************************************************/
+
+enum {
+ SIGNAL_QUALITY,
+ REGISTRATION_INFO,
+ NETWORK_MODE,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/*****************************************************************************/
+
+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 (MMModemGsmNetwork *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);
+}
+
+static void
+uint_call_done (MMModem *modem, guint32 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
+uint_call_not_supported (MMModemGsmNetwork *self,
+ MMModemUIntFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_uint_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
+reg_info_call_done (MMModemGsmNetwork *self,
+ MMModemGsmNetworkRegStatus status,
+ const char *oper_code,
+ const char *oper_name,
+ GError *error,
+ gpointer user_data)
+{
+ DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+
+ if (error)
+ dbus_g_method_return_error (context, error);
+ else {
+ GValueArray *array;
+ GValue value = { 0, };
+
+ array = g_value_array_new (3);
+
+ /* Status */
+ g_value_init (&value, G_TYPE_UINT);
+ g_value_set_uint (&value, (guint32) status);
+ g_value_array_append (array, &value);
+ g_value_unset (&value);
+
+ /* Operator code */
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, oper_code);
+ g_value_array_append (array, &value);
+ g_value_unset (&value);
+
+ /* Operator name */
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, oper_name);
+ g_value_array_append (array, &value);
+ g_value_unset (&value);
+
+ dbus_g_method_return (context, array);
+ }
+}
+
+static void
+reg_info_invoke (MMCallbackInfo *info)
+{
+ MMModemGsmNetworkRegInfoFn callback = (MMModemGsmNetworkRegInfoFn) info->callback;
+
+ callback (MM_MODEM_GSM_NETWORK (info->modem), 0, NULL, NULL, info->error, info->user_data);
+}
+
+static void
+reg_info_call_not_supported (MMModemGsmNetwork *self,
+ MMModemGsmNetworkRegInfoFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new_full (MM_MODEM (self), reg_info_invoke, G_CALLBACK (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
+scan_call_done (MMModemGsmNetwork *self,
+ GPtrArray *results,
+ 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, results);
+}
+
+static void
+gsm_network_scan_invoke (MMCallbackInfo *info)
+{
+ MMModemGsmNetworkScanFn callback = (MMModemGsmNetworkScanFn) info->callback;
+
+ callback (MM_MODEM_GSM_NETWORK (info->modem), NULL, info->error, info->user_data);
+}
+
+static void
+scan_call_not_supported (MMModemGsmNetwork *self,
+ MMModemGsmNetworkScanFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new_full (MM_MODEM (self), gsm_network_scan_invoke, G_CALLBACK (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_network_register (MMModemGsmNetwork *self,
+ const char *network_id,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->do_register)
+ MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->do_register (self, network_id, callback, user_data);
+ else
+ async_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_network_scan (MMModemGsmNetwork *self,
+ MMModemGsmNetworkScanFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->scan)
+ MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->scan (self, callback, user_data);
+ else
+ scan_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_network_set_apn (MMModemGsmNetwork *self,
+ const char *apn,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+ g_return_if_fail (apn != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_apn)
+ MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_apn (self, apn, callback, user_data);
+ else
+ async_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_network_get_signal_quality (MMModemGsmNetwork *self,
+ MMModemUIntFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_signal_quality)
+ MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_signal_quality (self, callback, user_data);
+ else
+ uint_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_network_set_band (MMModemGsmNetwork *self,
+ MMModemGsmBand band,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_band)
+ MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_band (self, band, callback, user_data);
+ else
+ async_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_network_get_band (MMModemGsmNetwork *self,
+ MMModemUIntFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_band)
+ MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_band (self, callback, user_data);
+ else
+ uint_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_network_set_mode (MMModemGsmNetwork *self,
+ MMModemGsmMode mode,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_network_mode)
+ MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_network_mode (self, mode, callback, user_data);
+ else
+ async_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_network_get_mode (MMModemGsmNetwork *self,
+ MMModemUIntFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_network_mode)
+ MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_network_mode (self, callback, user_data);
+ else
+ uint_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_network_get_registration_info (MMModemGsmNetwork *self,
+ MMModemGsmNetworkRegInfoFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_registration_info)
+ MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_registration_info (self, callback, user_data);
+ else
+ reg_info_call_not_supported (self, callback, user_data);
+}
+
+void
+mm_modem_gsm_network_signal_quality (MMModemGsmNetwork *self,
+ guint32 quality)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+
+ g_signal_emit (self, signals[SIGNAL_QUALITY], 0, quality);
+}
+
+void
+mm_modem_gsm_network_registration_info (MMModemGsmNetwork *self,
+ MMModemGsmNetworkRegStatus status,
+ const char *oper_code,
+ const char *oper_name)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+
+ g_signal_emit (self, signals[REGISTRATION_INFO], 0, status,
+ oper_code ? oper_code : "",
+ oper_name ? oper_name : "");
+}
+
+void
+mm_modem_gsm_network_mode (MMModemGsmNetwork *self,
+ MMModemGsmMode mode)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self));
+
+ g_signal_emit (self, signals[NETWORK_MODE], 0, mode);
+}
+
+/*****************************************************************************/
+
+static void
+impl_gsm_modem_register (MMModemGsmNetwork *modem,
+ const char *network_id,
+ DBusGMethodInvocation *context)
+{
+ const char *id;
+
+ /* DBus does not support NULL strings, so the caller should pass an empty string
+ for manual registration. */
+ if (strlen (network_id) < 1)
+ id = NULL;
+ else
+ id = network_id;
+
+ mm_modem_gsm_network_register (modem, id, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_scan (MMModemGsmNetwork *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_network_scan (modem, scan_call_done, context);
+}
+
+static void
+impl_gsm_modem_set_apn (MMModemGsmNetwork *modem,
+ const char *apn,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_network_set_apn (modem, apn, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_get_signal_quality (MMModemGsmNetwork *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_network_get_signal_quality (modem, uint_call_done, context);
+}
+
+static gboolean
+check_for_single_value (guint32 value)
+{
+ gboolean found = FALSE;
+ guint32 i;
+
+ for (i = 1; i <= 32; i++) {
+ if (value & 0x1) {
+ if (found)
+ return FALSE; /* More than one bit set */
+ found = TRUE;
+ }
+ value >>= 1;
+ }
+
+ return TRUE;
+}
+
+static void
+impl_gsm_modem_set_band (MMModemGsmNetwork *modem,
+ MMModemGsmBand band,
+ DBusGMethodInvocation *context)
+{
+ if (!check_for_single_value (band)) {
+ GError *error;
+
+ error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
+ "Invalid arguments (more than one value given)");
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ mm_modem_gsm_network_set_band (modem, band, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_get_band (MMModemGsmNetwork *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_network_get_band (modem, uint_call_done, context);
+}
+
+static void
+impl_gsm_modem_set_network_mode (MMModemGsmNetwork *modem,
+ MMModemGsmMode mode,
+ DBusGMethodInvocation *context)
+{
+ if (!check_for_single_value (mode)) {
+ GError *error;
+
+ error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
+ "Invalid arguments (more than one value given)");
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ mm_modem_gsm_network_set_mode (modem, mode, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_get_network_mode (MMModemGsmNetwork *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_network_get_mode (modem, uint_call_done, context);
+}
+
+static void
+impl_gsm_modem_get_reg_info (MMModemGsmNetwork *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_gsm_network_get_registration_info (modem, reg_info_call_done, context);
+}
+
+/*****************************************************************************/
+
+static void
+mm_modem_gsm_network_init (gpointer g_iface)
+{
+ GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+ static gboolean initialized = FALSE;
+
+ if (initialized)
+ return;
+
+ /* Signals */
+ signals[SIGNAL_QUALITY] =
+ g_signal_new ("signal-quality",
+ iface_type,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMModemGsmNetwork, signal_quality),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1,
+ G_TYPE_UINT);
+
+ signals[REGISTRATION_INFO] =
+ g_signal_new ("registration-info",
+ iface_type,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMModemGsmNetwork, registration_info),
+ NULL, NULL,
+ mm_marshal_VOID__UINT_STRING_STRING,
+ G_TYPE_NONE, 3,
+ G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING);
+
+ signals[NETWORK_MODE] =
+ g_signal_new ("network-mode",
+ iface_type,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMModemGsmNetwork, network_mode),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1,
+ G_TYPE_UINT);
+
+ initialized = TRUE;
+}
+
+GType
+mm_modem_gsm_network_get_type (void)
+{
+ static GType network_type = 0;
+
+ if (!G_UNLIKELY (network_type)) {
+ const GTypeInfo network_info = {
+ sizeof (MMModemGsmNetwork), /* class_size */
+ mm_modem_gsm_network_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL
+ };
+
+ network_type = g_type_register_static (G_TYPE_INTERFACE,
+ "MMModemGsmNetwork",
+ &network_info, 0);
+
+ g_type_interface_add_prerequisite (network_type, G_TYPE_OBJECT);
+ dbus_g_object_type_install_info (network_type, &dbus_glib_mm_modem_gsm_network_object_info);
+ }
+
+ return network_type;
+}
diff --git a/src/mm-modem-gsm-network.h b/src/mm-modem-gsm-network.h
new file mode 100644
index 0000000..493baec
--- /dev/null
+++ b/src/mm-modem-gsm-network.h
@@ -0,0 +1,164 @@
+/* -*- 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.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_MODEM_GSM_NETWORK_H
+#define MM_MODEM_GSM_NETWORK_H
+
+#include <mm-modem.h>
+#include <mm-modem-gsm.h>
+
+#define MM_TYPE_MODEM_GSM_NETWORK (mm_modem_gsm_network_get_type ())
+#define MM_MODEM_GSM_NETWORK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_GSM_NETWORK, MMModemGsmNetwork))
+#define MM_IS_MODEM_GSM_NETWORK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_GSM_NETWORK))
+#define MM_MODEM_GSM_NETWORK_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_GSM_NETWORK, MMModemGsmNetwork))
+
+typedef enum {
+ MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE = 0,
+ MM_MODEM_GSM_NETWORK_REG_STATUS_HOME = 1,
+ MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING = 2,
+ MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED = 3,
+ MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN = 4,
+ MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING = 5
+} MMModemGsmNetworkRegStatus;
+
+typedef struct _MMModemGsmNetwork MMModemGsmNetwork;
+
+typedef void (*MMModemGsmNetworkScanFn) (MMModemGsmNetwork *self,
+ GPtrArray *results,
+ GError *error,
+ gpointer user_data);
+
+typedef void (*MMModemGsmNetworkRegInfoFn) (MMModemGsmNetwork *self,
+ MMModemGsmNetworkRegStatus status,
+ const char *oper_code,
+ const char *oper_name,
+ GError *error,
+ gpointer user_data);
+
+struct _MMModemGsmNetwork {
+ GTypeInterface g_iface;
+
+ /* Methods */
+ /* 'register' is a reserved word */
+ void (*do_register) (MMModemGsmNetwork *self,
+ const char *network_id,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*scan) (MMModemGsmNetwork *self,
+ MMModemGsmNetworkScanFn callback,
+ gpointer user_data);
+
+ void (*set_apn) (MMModemGsmNetwork *self,
+ const char *apn,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*get_signal_quality) (MMModemGsmNetwork *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+ void (*set_band) (MMModemGsmNetwork *self,
+ MMModemGsmBand band,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*get_band) (MMModemGsmNetwork *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+ void (*set_network_mode) (MMModemGsmNetwork *self,
+ MMModemGsmMode mode,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*get_network_mode) (MMModemGsmNetwork *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+ void (*get_registration_info) (MMModemGsmNetwork *self,
+ MMModemGsmNetworkRegInfoFn callback,
+ gpointer user_data);
+
+ /* Signals */
+ void (*signal_quality) (MMModemGsmNetwork *self,
+ guint32 quality);
+
+ void (*registration_info) (MMModemGsmNetwork *self,
+ MMModemGsmNetworkRegStatus status,
+ const char *open_code,
+ const char *oper_name);
+
+ void (*network_mode) (MMModemGsmNetwork *self,
+ MMModemGsmMode mode);
+};
+
+GType mm_modem_gsm_network_get_type (void);
+
+void mm_modem_gsm_network_register (MMModemGsmNetwork *self,
+ const char *network_id,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_network_scan (MMModemGsmNetwork *self,
+ MMModemGsmNetworkScanFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_network_set_apn (MMModemGsmNetwork *self,
+ const char *apn,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_network_get_signal_quality (MMModemGsmNetwork *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_network_set_band (MMModemGsmNetwork *self,
+ MMModemGsmBand band,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_network_get_band (MMModemGsmNetwork *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_network_set_mode (MMModemGsmNetwork *self,
+ MMModemGsmMode mode,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_network_get_mode (MMModemGsmNetwork *self,
+ MMModemUIntFn callback,
+ gpointer user_data);
+
+void mm_modem_gsm_network_get_registration_info (MMModemGsmNetwork *self,
+ MMModemGsmNetworkRegInfoFn callback,
+ gpointer user_data);
+
+/* Protected */
+
+void mm_modem_gsm_network_signal_quality (MMModemGsmNetwork *self,
+ guint32 quality);
+
+void mm_modem_gsm_network_registration_info (MMModemGsmNetwork *self,
+ MMModemGsmNetworkRegStatus status,
+ const char *oper_code,
+ const char *oper_name);
+
+void mm_modem_gsm_network_mode (MMModemGsmNetwork *self,
+ MMModemGsmMode mode);
+
+#endif /* MM_MODEM_GSM_NETWORK_H */
diff --git a/src/mm-modem-gsm-sms.c b/src/mm-modem-gsm-sms.c
new file mode 100644
index 0000000..e8ec074
--- /dev/null
+++ b/src/mm-modem-gsm-sms.c
@@ -0,0 +1,320 @@
+/* -*- 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) 2009 Novell, Inc.
+ */
+
+#include <string.h>
+#include <dbus/dbus-glib.h>
+
+#include "mm-modem-gsm-sms.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+#include "mm-marshal.h"
+
+static void impl_gsm_modem_sms_delete (MMModemGsmSms *modem,
+ guint idx,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_sms_get (MMModemGsmSms *modem,
+ guint idx,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_sms_get_format (MMModemGsmSms *modem,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_sms_set_format (MMModemGsmSms *modem,
+ guint format,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_sms_get_smsc (MMModemGsmSms *modem,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_sms_set_smsc (MMModemGsmSms *modem,
+ const char *smsc,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_sms_list (MMModemGsmSms *modem,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_sms_save (MMModemGsmSms *modem,
+ GHashTable *properties,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_sms_send (MMModemGsmSms *modem,
+ GHashTable *properties,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_sms_send_from_storage (MMModemGsmSms *modem,
+ guint idx,
+ DBusGMethodInvocation *context);
+
+static void impl_gsm_modem_sms_set_indication (MMModemGsmSms *modem,
+ guint mode,
+ guint mt,
+ guint bm,
+ guint ds,
+ guint bfr,
+ DBusGMethodInvocation *context);
+
+#include "mm-modem-gsm-sms-glue.h"
+
+enum {
+ SMS_RECEIVED,
+ COMPLETED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/*****************************************************************************/
+
+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 (MMModemGsmSms *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_sms_send (MMModemGsmSms *self,
+ const char *number,
+ const char *text,
+ const char *smsc,
+ guint validity,
+ guint class,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_GSM_SMS (self));
+ g_return_if_fail (number != NULL);
+ g_return_if_fail (text != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GSM_SMS_GET_INTERFACE (self)->send)
+ MM_MODEM_GSM_SMS_GET_INTERFACE (self)->send (self, number, text, smsc, validity, class, callback, user_data);
+ else
+ async_call_not_supported (self, callback, user_data);
+
+}
+
+/*****************************************************************************/
+
+static void
+impl_gsm_modem_sms_delete (MMModemGsmSms *modem,
+ guint idx,
+ DBusGMethodInvocation *context)
+{
+ async_call_not_supported (modem, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_sms_get (MMModemGsmSms *modem,
+ guint idx,
+ DBusGMethodInvocation *context)
+{
+ async_call_not_supported (modem, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_sms_get_format (MMModemGsmSms *modem,
+ DBusGMethodInvocation *context)
+{
+ async_call_not_supported (modem, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_sms_set_format (MMModemGsmSms *modem,
+ guint format,
+ DBusGMethodInvocation *context)
+{
+ async_call_not_supported (modem, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_sms_get_smsc (MMModemGsmSms *modem,
+ DBusGMethodInvocation *context)
+{
+ async_call_not_supported (modem, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_sms_set_smsc (MMModemGsmSms *modem,
+ const char *smsc,
+ DBusGMethodInvocation *context)
+{
+ async_call_not_supported (modem, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_sms_list (MMModemGsmSms *modem,
+ DBusGMethodInvocation *context)
+{
+ async_call_not_supported (modem, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_sms_save (MMModemGsmSms *modem,
+ GHashTable *properties,
+ DBusGMethodInvocation *context)
+{
+ async_call_not_supported (modem, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_sms_send (MMModemGsmSms *modem,
+ GHashTable *properties,
+ DBusGMethodInvocation *context)
+{
+ GValue *value;
+ const char *number = NULL;
+ const char *text = NULL ;
+ const char *smsc = NULL;
+ GError *error = NULL;
+ guint validity = 0;
+ guint class = 0;
+
+ value = (GValue *) g_hash_table_lookup (properties, "number");
+ if (value)
+ number = g_value_get_string (value);
+
+ value = (GValue *) g_hash_table_lookup (properties, "text");
+ if (value)
+ text = g_value_get_string (value);
+
+ value = (GValue *) g_hash_table_lookup (properties, "smsc");
+ if (value)
+ smsc = g_value_get_string (value);
+
+ value = (GValue *) g_hash_table_lookup (properties, "validity");
+ if (value)
+ validity = g_value_get_uint (value);
+
+ value = (GValue *) g_hash_table_lookup (properties, "class");
+ if (value)
+ class = g_value_get_uint (value);
+
+ if (!number)
+ error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Missing number");
+ else if (!text)
+ error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Missing message text");
+
+ if (error) {
+ async_call_done (MM_MODEM (modem), error, context);
+ g_error_free (error);
+ } else
+ mm_modem_gsm_sms_send (modem, number, text, smsc, validity, class, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_sms_send_from_storage (MMModemGsmSms *modem,
+ guint idx,
+ DBusGMethodInvocation *context)
+{
+ async_call_not_supported (modem, async_call_done, context);
+}
+
+static void
+impl_gsm_modem_sms_set_indication (MMModemGsmSms *modem,
+ guint mode,
+ guint mt,
+ guint bm,
+ guint ds,
+ guint bfr,
+ DBusGMethodInvocation *context)
+{
+ async_call_not_supported (modem, async_call_done, context);
+}
+
+/*****************************************************************************/
+
+static void
+mm_modem_gsm_sms_init (gpointer g_iface)
+{
+ GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+ static gboolean initialized = FALSE;
+
+ if (initialized)
+ return;
+
+ /* Signals */
+ signals[SMS_RECEIVED] =
+ g_signal_new ("sms-received",
+ iface_type,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMModemGsmSms, sms_received),
+ NULL, NULL,
+ mm_marshal_VOID__UINT_BOOLEAN,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_BOOLEAN);
+
+ signals[COMPLETED] =
+ g_signal_new ("completed",
+ iface_type,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMModemGsmSms, completed),
+ NULL, NULL,
+ mm_marshal_VOID__UINT_BOOLEAN,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_BOOLEAN);
+
+ initialized = TRUE;
+}
+
+GType
+mm_modem_gsm_sms_get_type (void)
+{
+ static GType sms_type = 0;
+
+ if (!G_UNLIKELY (sms_type)) {
+ const GTypeInfo sms_info = {
+ sizeof (MMModemGsmSms), /* class_size */
+ mm_modem_gsm_sms_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL
+ };
+
+ sms_type = g_type_register_static (G_TYPE_INTERFACE,
+ "MMModemGsmSms",
+ &sms_info, 0);
+
+ g_type_interface_add_prerequisite (sms_type, G_TYPE_OBJECT);
+ dbus_g_object_type_install_info (sms_type, &dbus_glib_mm_modem_gsm_sms_object_info);
+ }
+
+ return sms_type;
+}
diff --git a/src/mm-modem-gsm-sms.h b/src/mm-modem-gsm-sms.h
new file mode 100644
index 0000000..79a5bb0
--- /dev/null
+++ b/src/mm-modem-gsm-sms.h
@@ -0,0 +1,62 @@
+/* -*- 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) 2009 Novell, Inc.
+ */
+
+#ifndef MM_MODEM_GSM_SMS_H
+#define MM_MODEM_GSM_SMS_H
+
+#include <mm-modem.h>
+
+#define MM_TYPE_MODEM_GSM_SMS (mm_modem_gsm_sms_get_type ())
+#define MM_MODEM_GSM_SMS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_GSM_SMS, MMModemGsmSms))
+#define MM_IS_MODEM_GSM_SMS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_GSM_SMS))
+#define MM_MODEM_GSM_SMS_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_GSM_SMS, MMModemGsmSms))
+
+typedef struct _MMModemGsmSms MMModemGsmSms;
+
+struct _MMModemGsmSms {
+ GTypeInterface g_iface;
+
+ /* Methods */
+ void (*send) (MMModemGsmSms *modem,
+ const char *number,
+ const char *text,
+ const char *smsc,
+ guint validity,
+ guint class,
+ MMModemFn callback,
+ gpointer user_data);
+
+ /* Signals */
+ void (*sms_received) (MMModemGsmSms *self,
+ guint32 index,
+ gboolean completed);
+
+ void (*completed) (MMModemGsmSms *self,
+ guint32 index,
+ gboolean completed);
+};
+
+GType mm_modem_gsm_sms_get_type (void);
+
+void mm_modem_gsm_sms_send (MMModemGsmSms *self,
+ const char *number,
+ const char *text,
+ const char *smsc,
+ guint validity,
+ guint class,
+ MMModemFn callback,
+ gpointer user_data);
+
+#endif /* MM_MODEM_GSM_SMS_H */
diff --git a/src/mm-modem-gsm.h b/src/mm-modem-gsm.h
new file mode 100644
index 0000000..852ff85
--- /dev/null
+++ b/src/mm-modem-gsm.h
@@ -0,0 +1,57 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_MODEM_GSM_H
+#define MM_MODEM_GSM_H
+
+typedef enum {
+ MM_MODEM_GSM_MODE_UNKNOWN = 0x00000000,
+ MM_MODEM_GSM_MODE_ANY = 0x00000001,
+ MM_MODEM_GSM_MODE_GPRS = 0x00000002,
+ MM_MODEM_GSM_MODE_EDGE = 0x00000004,
+ MM_MODEM_GSM_MODE_UMTS = 0x00000008,
+ MM_MODEM_GSM_MODE_HSDPA = 0x00000010,
+ MM_MODEM_GSM_MODE_2G_PREFERRED = 0x00000020,
+ MM_MODEM_GSM_MODE_3G_PREFERRED = 0x00000040,
+ MM_MODEM_GSM_MODE_2G_ONLY = 0x00000080,
+ MM_MODEM_GSM_MODE_3G_ONLY = 0x00000100,
+ MM_MODEM_GSM_MODE_HSUPA = 0x00000200,
+ MM_MODEM_GSM_MODE_HSPA = 0x00000400,
+
+ MM_MODEM_GSM_MODE_LAST = MM_MODEM_GSM_MODE_HSPA
+} MMModemGsmMode;
+
+typedef enum {
+ MM_MODEM_GSM_BAND_UNKNOWN = 0x00000000,
+ MM_MODEM_GSM_BAND_ANY = 0x00000001,
+ MM_MODEM_GSM_BAND_EGSM = 0x00000002, /* 900 MHz */
+ MM_MODEM_GSM_BAND_DCS = 0x00000004, /* 1800 MHz */
+ MM_MODEM_GSM_BAND_PCS = 0x00000008, /* 1900 MHz */
+ MM_MODEM_GSM_BAND_G850 = 0x00000010, /* 850 MHz */
+ MM_MODEM_GSM_BAND_U2100 = 0x00000020, /* WCDMA 3GPP UMTS 2100 MHz (Class I) */
+ MM_MODEM_GSM_BAND_U1800 = 0x00000040, /* WCDMA 3GPP UMTS 1800 MHz (Class III) */
+ MM_MODEM_GSM_BAND_U17IV = 0x00000080, /* WCDMA 3GPP AWS 1700/2100 MHz (Class IV) */
+ MM_MODEM_GSM_BAND_U800 = 0x00000100, /* WCDMA 3GPP UMTS 800 MHz (Class VI) */
+ MM_MODEM_GSM_BAND_U850 = 0x00000200, /* WCDMA 3GPP UMTS 850 MHz (Class V) */
+ MM_MODEM_GSM_BAND_U900 = 0x00000400, /* WCDMA 3GPP UMTS 900 MHz (Class VIII) */
+ MM_MODEM_GSM_BAND_U17IX = 0x00000800, /* WCDMA 3GPP UMTS 1700 MHz (Class IX) */
+
+ MM_MODEM_GSM_BAND_LAST = MM_MODEM_GSM_BAND_U17IX
+} MMModemGsmBand;
+
+
+#endif /* MM_MODEM_GSM_H */
+
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
new file mode 100644
index 0000000..1741b5f
--- /dev/null
+++ b/src/mm-modem-helpers.c
@@ -0,0 +1,203 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ */
+
+#include <glib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "mm-errors.h"
+#include "mm-modem-helpers.h"
+
+static void
+save_scan_value (GHashTable *hash, const char *key, GMatchInfo *info, guint32 num)
+{
+ char *quoted;
+ size_t len;
+
+ g_return_if_fail (info != NULL);
+
+ quoted = g_match_info_fetch (info, num);
+ if (!quoted)
+ return;
+
+ len = strlen (quoted);
+
+ /* Unquote the item if needed */
+ if ((len >= 2) && (quoted[0] == '"') && (quoted[len - 1] == '"')) {
+ quoted[0] = ' ';
+ quoted[len - 1] = ' ';
+ quoted = g_strstrip (quoted);
+ }
+
+ if (!strlen (quoted)) {
+ g_free (quoted);
+ return;
+ }
+
+ g_hash_table_insert (hash, g_strdup (key), quoted);
+}
+
+/* If the response was successfully parsed (even if no valid entries were
+ * found) the pointer array will be returned.
+ */
+GPtrArray *
+mm_gsm_parse_scan_response (const char *reply, GError **error)
+{
+ /* Got valid reply */
+ GPtrArray *results = NULL;
+ GRegex *r;
+ GMatchInfo *match_info;
+ GError *err = NULL;
+ gboolean umts_format = TRUE;
+
+ g_return_val_if_fail (reply != NULL, NULL);
+ if (error)
+ g_return_val_if_fail (*error == NULL, NULL);
+
+ if (!strstr (reply, "+COPS: ")) {
+ g_set_error_literal (error,
+ MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Could not parse scan results.");
+ return NULL;
+ }
+
+ reply = strstr (reply, "+COPS: ") + 7;
+
+ /* Cell access technology (GSM, UTRAN, etc) got added later and not all
+ * modems implement it. Some modesm have quirks that make it hard to
+ * use one regular experession for matching both pre-UMTS and UMTS
+ * responses. So try UMTS-format first and fall back to pre-UMTS if
+ * we get no UMTS-formst matches.
+ */
+
+ /* Quirk: Sony-Ericsson TM-506 sometimes includes a stray ')' like so,
+ * which is what makes it hard to match both pre-UMTS and UMTS in
+ * the same regex:
+ *
+ * +COPS: (2,"","T-Mobile","31026",0),(1,"AT&T","AT&T","310410"),0)
+ */
+
+ r = g_regex_new ("\\((\\d),([^,\\)]*),([^,\\)]*),([^,\\)]*)[\\)]?,(\\d)\\)", G_REGEX_UNGREEDY, 0, NULL);
+ if (err) {
+ g_error ("Invalid regular expression: %s", err->message);
+ g_error_free (err);
+ g_set_error_literal (error,
+ MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Could not parse scan results.");
+ return NULL;
+ }
+
+ /* If we didn't get any hits, try the pre-UMTS format match */
+ if (!g_regex_match (r, reply, 0, &match_info)) {
+ g_regex_unref (r);
+ if (match_info) {
+ g_match_info_free (match_info);
+ match_info = NULL;
+ }
+
+ /* Pre-UMTS format doesn't include the cell access technology after
+ * the numeric operator element.
+ *
+ * Ex: Motorola C-series (BUSlink SCWi275u) like so:
+ *
+ * +COPS: (2,"T-Mobile","","310260"),(0,"Cingular Wireless","","310410")
+ */
+
+ /* Quirk: Some Nokia phones (N80) don't send the quotes for empty values:
+ *
+ * +COPS: (2,"T - Mobile",,"31026"),(1,"Einstein PCS",,"31064"),(1,"Cingular",,"31041"),,(0,1,3),(0,2)
+ */
+
+ r = g_regex_new ("\\((\\d),([^,\\)]*),([^,\\)]*),([^\\)]*)\\)", G_REGEX_UNGREEDY, 0, NULL);
+ if (err) {
+ g_error ("Invalid regular expression: %s", err->message);
+ g_error_free (err);
+ g_set_error_literal (error,
+ MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Could not parse scan results.");
+ return NULL;
+ }
+
+ g_regex_match (r, reply, 0, &match_info);
+ umts_format = FALSE;
+ }
+
+ /* Parse the results */
+ results = g_ptr_array_new ();
+ while (g_match_info_matches (match_info)) {
+ GHashTable *hash;
+ char *access_tech = NULL;
+ const char *tmp;
+ gboolean valid = FALSE;
+
+ hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ save_scan_value (hash, MM_SCAN_TAG_STATUS, match_info, 1);
+ save_scan_value (hash, MM_SCAN_TAG_OPER_LONG, match_info, 2);
+ save_scan_value (hash, MM_SCAN_TAG_OPER_SHORT, match_info, 3);
+ save_scan_value (hash, MM_SCAN_TAG_OPER_NUM, match_info, 4);
+
+ /* Only try for access technology with UMTS-format matches */
+ if (umts_format)
+ access_tech = g_match_info_fetch (match_info, 5);
+ if (access_tech && (strlen (access_tech) == 1)) {
+ /* Recognized access technologies are between '0' and '6' inclusive... */
+ if ((access_tech[0] >= '0') && (access_tech[0] <= '6'))
+ g_hash_table_insert (hash, g_strdup (MM_SCAN_TAG_ACCESS_TECH), access_tech);
+ } else
+ g_free (access_tech);
+
+ /* If the operator number isn't valid (ie, at least 5 digits),
+ * ignore the scan result; it's probably the parameter stuff at the
+ * end of the +COPS response. The regex will sometimes catch this
+ * but there's no good way to ignore it.
+ */
+ tmp = g_hash_table_lookup (hash, MM_SCAN_TAG_OPER_NUM);
+ if (tmp && (strlen (tmp) >= 5)) {
+ valid = TRUE;
+ while (*tmp) {
+ if (!isdigit (*tmp) && (*tmp != '-')) {
+ valid = FALSE;
+ break;
+ }
+ tmp++;
+ }
+
+ if (valid)
+ g_ptr_array_add (results, hash);
+ }
+
+ if (!valid)
+ g_hash_table_destroy (hash);
+
+ g_match_info_next (match_info, NULL);
+ }
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+
+ return results;
+}
+
+void
+mm_gsm_destroy_scan_data (gpointer data)
+{
+ GPtrArray *results = (GPtrArray *) data;
+
+ g_ptr_array_foreach (results, (GFunc) g_hash_table_destroy, NULL);
+ g_ptr_array_free (results, TRUE);
+}
+
diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h
new file mode 100644
index 0000000..ddc9cbc
--- /dev/null
+++ b/src/mm-modem-helpers.h
@@ -0,0 +1,31 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ */
+
+#ifndef MM_MODEM_HELPERS_H
+#define MM_MODEM_HELPERS_H
+
+#define MM_SCAN_TAG_STATUS "status"
+#define MM_SCAN_TAG_OPER_LONG "operator-long"
+#define MM_SCAN_TAG_OPER_SHORT "operator-short"
+#define MM_SCAN_TAG_OPER_NUM "operator-num"
+#define MM_SCAN_TAG_ACCESS_TECH "access-tech"
+
+GPtrArray *mm_gsm_parse_scan_response (const char *reply, GError **error);
+
+void mm_gsm_destroy_scan_data (gpointer data);
+
+#endif /* MM_MODEM_HELPERS_H */
+
diff --git a/src/mm-modem-simple.c b/src/mm-modem-simple.c
new file mode 100644
index 0000000..415fd44
--- /dev/null
+++ b/src/mm-modem-simple.c
@@ -0,0 +1,156 @@
+/* -*- 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) 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include <dbus/dbus-glib.h>
+
+#include "mm-modem-simple.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+
+static void impl_modem_simple_connect (MMModemSimple *self, GHashTable *properties, DBusGMethodInvocation *context);
+static void impl_modem_simple_get_status (MMModemSimple *self, DBusGMethodInvocation *context);
+
+#include "mm-modem-simple-glue.h"
+
+void
+mm_modem_simple_connect (MMModemSimple *self,
+ GHashTable *properties,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_SIMPLE (self));
+ g_return_if_fail (properties != NULL);
+
+ if (MM_MODEM_SIMPLE_GET_INTERFACE (self)->connect)
+ MM_MODEM_SIMPLE_GET_INTERFACE (self)->connect (self, properties, callback, user_data);
+ else {
+ 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);
+ }
+}
+
+static void
+simple_get_status_invoke (MMCallbackInfo *info)
+{
+ MMModemSimpleGetStatusFn callback = (MMModemSimpleGetStatusFn) info->callback;
+
+ callback (MM_MODEM_SIMPLE (info->modem), NULL, info->error, info->user_data);
+}
+
+void
+mm_modem_simple_get_status (MMModemSimple *self,
+ MMModemSimpleGetStatusFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_SIMPLE (self));
+
+ if (MM_MODEM_SIMPLE_GET_INTERFACE (self)->get_status)
+ MM_MODEM_SIMPLE_GET_INTERFACE (self)->get_status (self, callback, user_data);
+ else {
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new_full (MM_MODEM (self),
+ simple_get_status_invoke,
+ G_CALLBACK (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
+impl_modem_simple_connect (MMModemSimple *self,
+ GHashTable *properties,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_simple_connect (self, properties, async_call_done, context);
+}
+
+static void
+get_status_done (MMModemSimple *modem,
+ GHashTable *properties,
+ 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, properties);
+}
+
+static void
+impl_modem_simple_get_status (MMModemSimple *self,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_simple_get_status (self, get_status_done, context);
+}
+
+/*****************************************************************************/
+
+static void
+mm_modem_simple_init (gpointer g_iface)
+{
+}
+
+GType
+mm_modem_simple_get_type (void)
+{
+ static GType modem_type = 0;
+
+ if (!G_UNLIKELY (modem_type)) {
+ const GTypeInfo modem_info = {
+ sizeof (MMModemSimple), /* class_size */
+ mm_modem_simple_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL
+ };
+
+ modem_type = g_type_register_static (G_TYPE_INTERFACE,
+ "MMModemSimple",
+ &modem_info, 0);
+
+ g_type_interface_add_prerequisite (modem_type, G_TYPE_OBJECT);
+ dbus_g_object_type_install_info (modem_type, &dbus_glib_mm_modem_simple_object_info);
+ }
+
+ return modem_type;
+}
diff --git a/src/mm-modem-simple.h b/src/mm-modem-simple.h
new file mode 100644
index 0000000..03b5561
--- /dev/null
+++ b/src/mm-modem-simple.h
@@ -0,0 +1,59 @@
+/* -*- 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) 2009 Novell, Inc.
+ */
+
+#ifndef MM_MODEM_SIMPLE_H
+#define MM_MODEM_SIMPLE_H
+
+#include <glib-object.h>
+#include <mm-modem.h>
+
+#define MM_TYPE_MODEM_SIMPLE (mm_modem_simple_get_type ())
+#define MM_MODEM_SIMPLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_SIMPLE, MMModemSimple))
+#define MM_IS_MODEM_SIMPLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_SIMPLE))
+#define MM_MODEM_SIMPLE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_SIMPLE, MMModemSimple))
+
+typedef struct _MMModemSimple MMModemSimple;
+
+typedef void (*MMModemSimpleGetStatusFn) (MMModemSimple *modem,
+ GHashTable *properties,
+ GError *error,
+ gpointer user_data);
+
+struct _MMModemSimple {
+ GTypeInterface g_iface;
+
+ /* Methods */
+ void (*connect) (MMModemSimple *self,
+ GHashTable *properties,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*get_status) (MMModemSimple *self,
+ MMModemSimpleGetStatusFn callback,
+ gpointer user_data);
+};
+
+GType mm_modem_simple_get_type (void);
+
+void mm_modem_simple_connect (MMModemSimple *self,
+ GHashTable *properties,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_simple_get_status (MMModemSimple *self,
+ MMModemSimpleGetStatusFn callback,
+ gpointer user_data);
+
+#endif /* MM_MODEM_SIMPLE_H */
diff --git a/src/mm-modem.c b/src/mm-modem.c
new file mode 100644
index 0000000..a65d883
--- /dev/null
+++ b/src/mm-modem.c
@@ -0,0 +1,738 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <dbus/dbus-glib.h>
+#include "mm-modem.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+#include "mm-marshal.h"
+
+static void impl_modem_enable (MMModem *modem, gboolean enable, DBusGMethodInvocation *context);
+static void impl_modem_connect (MMModem *modem, const char *number, DBusGMethodInvocation *context);
+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);
+
+#include "mm-modem-glue.h"
+
+/* Should be used from callbacks to check whether the modem was removed after
+ * the callback's operation was started, but before the callback itself was
+ * called, in which case the MMModem passed to the callback is NULL.
+ */
+GError *
+mm_modem_check_removed (MMModem *self, const GError *error)
+{
+ if (!self) {
+ /* If the modem was NULL, the error *should* have been
+ * MM_MODEM_ERROR_REMOVED. If it wasn't, make it that.
+ */
+ return g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_REMOVED,
+ "The modem was removed.");
+ }
+
+ return error ? g_error_copy (error) : NULL;
+}
+
+static void
+async_op_not_supported (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new (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);
+}
+
+void
+mm_modem_enable (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMModemState state;
+
+ g_return_if_fail (MM_IS_MODEM (self));
+ g_return_if_fail (callback != NULL);
+
+ state = mm_modem_get_state (self);
+ if (state >= MM_MODEM_STATE_ENABLED) {
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new (self, callback, user_data);
+
+ if (state == MM_MODEM_STATE_ENABLING) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
+ "The device is already being enabled.");
+ } else {
+ /* Already enabled */
+ }
+
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ if (MM_MODEM_GET_INTERFACE (self)->enable)
+ MM_MODEM_GET_INTERFACE (self)->enable (self, callback, user_data);
+ else
+ async_op_not_supported (self, callback, user_data);
+}
+
+static void
+finish_disable (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ if (MM_MODEM_GET_INTERFACE (self)->disable)
+ MM_MODEM_GET_INTERFACE (self)->disable (self, callback, user_data);
+ else
+ async_op_not_supported (self, callback, user_data);
+}
+
+typedef struct {
+ MMModemFn callback;
+ gpointer user_data;
+} DisableDisconnectInfo;
+
+static void
+disable_disconnect_done (MMModem *self,
+ GError *error,
+ gpointer user_data)
+{
+ DisableDisconnectInfo *cb_data = user_data;
+ GError *tmp_error = NULL;
+
+ /* Check for modem removal */
+ if (g_error_matches (error, MM_MODEM_ERROR, MM_MODEM_ERROR_REMOVED))
+ tmp_error = g_error_copy (error);
+ else if (!self) {
+ tmp_error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_REMOVED,
+ "The modem was removed.");
+ }
+
+ /* And send an immediate error reply if the modem was removed */
+ if (tmp_error) {
+ cb_data->callback (NULL, tmp_error, cb_data->user_data);
+ g_free (cb_data);
+ g_error_free (tmp_error);
+ return;
+ }
+
+ if (error) {
+ /* Don't really care what the error was; log it and proceed to disable */
+ g_warning ("%s: (%s): error disconnecting the modem while disabling: (%d) %s",
+ __func__,
+ mm_modem_get_device (self),
+ error ? error->code : -1,
+ error && error->message ? error->message : "(unknown)");
+ }
+ finish_disable (self, cb_data->callback, cb_data->user_data);
+ g_free (cb_data);
+}
+
+void
+mm_modem_disable (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMModemState state;
+
+ g_return_if_fail (MM_IS_MODEM (self));
+ g_return_if_fail (callback != NULL);
+
+ state = mm_modem_get_state (self);
+ if (state <= MM_MODEM_STATE_DISABLING) {
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new (self, callback, user_data);
+
+ if (state == MM_MODEM_STATE_DISABLING) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
+ "The device is already being disabled.");
+ } else {
+ /* Already disabled */
+ }
+
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ /* If the modem is connected, disconnect it */
+ if (state >= MM_MODEM_STATE_CONNECTING) {
+ DisableDisconnectInfo *cb_data;
+
+ cb_data = g_malloc0 (sizeof (DisableDisconnectInfo));
+ cb_data->callback = callback;
+ cb_data->user_data = user_data;
+ mm_modem_disconnect (self, disable_disconnect_done, cb_data);
+ } else
+ finish_disable (self, callback, user_data);
+}
+
+static void
+impl_modem_enable (MMModem *modem,
+ gboolean enable,
+ DBusGMethodInvocation *context)
+{
+ if (enable)
+ mm_modem_enable (modem, async_call_done, context);
+ else
+ mm_modem_disable (modem, async_call_done, context);
+}
+
+void
+mm_modem_connect (MMModem *self,
+ const char *number,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMModemState state;
+
+ g_return_if_fail (MM_IS_MODEM (self));
+ g_return_if_fail (callback != NULL);
+ g_return_if_fail (number != NULL);
+
+ state = mm_modem_get_state (self);
+ if (state >= MM_MODEM_STATE_CONNECTING) {
+ MMCallbackInfo *info;
+
+ /* Already connecting */
+ info = mm_callback_info_new (self, callback, user_data);
+ if (state == MM_MODEM_STATE_CONNECTING) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
+ "The device is already being connected.");
+ } else {
+ /* already connected */
+ }
+
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ if (MM_MODEM_GET_INTERFACE (self)->connect)
+ MM_MODEM_GET_INTERFACE (self)->connect (self, number, callback, user_data);
+ else
+ async_op_not_supported (self, callback, user_data);
+}
+
+static void
+impl_modem_connect (MMModem *modem,
+ const char *number,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_connect (modem, number, async_call_done, context);
+}
+
+static void
+get_ip4_invoke (MMCallbackInfo *info)
+{
+ MMModemIp4Fn callback = (MMModemIp4Fn) info->callback;
+
+ callback (info->modem,
+ GPOINTER_TO_UINT (mm_callback_info_get_data (info, "ip4-address")),
+ (GArray *) mm_callback_info_get_data (info, "ip4-dns"),
+ info->error, info->user_data);
+}
+
+void
+mm_modem_get_ip4_config (MMModem *self,
+ MMModemIp4Fn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GET_INTERFACE (self)->get_ip4_config)
+ MM_MODEM_GET_INTERFACE (self)->get_ip4_config (self, callback, user_data);
+ else {
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new_full (self,
+ get_ip4_invoke,
+ G_CALLBACK (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
+value_array_add_uint (GValueArray *array, guint32 i)
+{
+ GValue value = { 0, };
+
+ g_value_init (&value, G_TYPE_UINT);
+ g_value_set_uint (&value, i);
+ g_value_array_append (array, &value);
+ g_value_unset (&value);
+}
+
+static void
+get_ip4_done (MMModem *modem,
+ guint32 address,
+ GArray *dns,
+ GError *error,
+ gpointer user_data)
+{
+ DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+
+ if (error)
+ dbus_g_method_return_error (context, error);
+ else {
+ GValueArray *array;
+ guint32 dns1 = 0;
+ guint32 dns2 = 0;
+ guint32 dns3 = 0;
+
+ array = g_value_array_new (4);
+
+ if (dns) {
+ if (dns->len > 0)
+
+ dns1 = g_array_index (dns, guint32, 0);
+ if (dns->len > 1)
+ dns2 = g_array_index (dns, guint32, 1);
+ if (dns->len > 2)
+ dns3 = g_array_index (dns, guint32, 2);
+ }
+
+ value_array_add_uint (array, address);
+ value_array_add_uint (array, dns1);
+ value_array_add_uint (array, dns2);
+ value_array_add_uint (array, dns3);
+
+ dbus_g_method_return (context, array);
+ }
+}
+
+static void
+impl_modem_get_ip4_config (MMModem *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_get_ip4_config (modem, get_ip4_done, context);
+}
+
+void
+mm_modem_disconnect (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMModemState state;
+
+ g_return_if_fail (MM_IS_MODEM (self));
+ g_return_if_fail (callback != NULL);
+
+ state = mm_modem_get_state (self);
+ if (state <= MM_MODEM_STATE_DISCONNECTING) {
+ MMCallbackInfo *info;
+
+ /* Already connecting */
+ info = mm_callback_info_new (self, callback, user_data);
+ if (state == MM_MODEM_STATE_DISCONNECTING) {
+ info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
+ "The device is already being disconnected.");
+ } else {
+ /* already disconnected */
+ }
+
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ if (MM_MODEM_GET_INTERFACE (self)->disconnect)
+ MM_MODEM_GET_INTERFACE (self)->disconnect (self, callback, user_data);
+ else
+ async_op_not_supported (self, callback, user_data);
+}
+
+static void
+impl_modem_disconnect (MMModem *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_disconnect (modem, async_call_done, context);
+}
+
+static void
+info_call_done (MMModem *self,
+ const char *manufacturer,
+ const char *model,
+ const char *version,
+ GError *error,
+ gpointer user_data)
+{
+ DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+
+ if (error)
+ dbus_g_method_return_error (context, error);
+ else {
+ GValueArray *array;
+ GValue value = { 0, };
+
+ array = g_value_array_new (3);
+
+ /* Manufacturer */
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, manufacturer);
+ g_value_array_append (array, &value);
+ g_value_unset (&value);
+
+ /* Model */
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, model);
+ g_value_array_append (array, &value);
+ g_value_unset (&value);
+
+ /* Version */
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, version);
+ g_value_array_append (array, &value);
+ g_value_unset (&value);
+
+ dbus_g_method_return (context, array);
+ }
+}
+
+static void
+info_invoke (MMCallbackInfo *info)
+{
+ MMModemInfoFn callback = (MMModemInfoFn) info->callback;
+
+ callback (info->modem, NULL, NULL, NULL, info->error, info->user_data);
+}
+
+static void
+info_call_not_supported (MMModem *self,
+ MMModemInfoFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+
+ info = mm_callback_info_new_full (MM_MODEM (self), info_invoke, G_CALLBACK (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_get_info (MMModem *self,
+ MMModemInfoFn callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM (self));
+ g_return_if_fail (callback != NULL);
+
+ if (MM_MODEM_GET_INTERFACE (self)->get_info)
+ MM_MODEM_GET_INTERFACE (self)->get_info (self, callback, user_data);
+ else
+ info_call_not_supported (self, callback, user_data);
+}
+
+static void
+impl_modem_get_info (MMModem *modem,
+ DBusGMethodInvocation *context)
+{
+ mm_modem_get_info (modem, info_call_done, context);
+}
+
+/*****************************************************************************/
+
+gboolean
+mm_modem_owns_port (MMModem *self,
+ const char *subsys,
+ const char *name)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (MM_IS_MODEM (self), FALSE);
+ g_return_val_if_fail (subsys, FALSE);
+ g_return_val_if_fail (name, FALSE);
+
+ g_assert (MM_MODEM_GET_INTERFACE (self)->owns_port);
+ return MM_MODEM_GET_INTERFACE (self)->owns_port (self, subsys, name);
+}
+
+gboolean
+mm_modem_grab_port (MMModem *self,
+ const char *subsys,
+ const char *name,
+ MMPortType suggested_type,
+ gpointer user_data,
+ GError **error)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (MM_IS_MODEM (self), FALSE);
+ g_return_val_if_fail (subsys, FALSE);
+ g_return_val_if_fail (name, FALSE);
+
+ g_assert (MM_MODEM_GET_INTERFACE (self)->grab_port);
+ return MM_MODEM_GET_INTERFACE (self)->grab_port (self, subsys, name, suggested_type, user_data, error);
+}
+
+void
+mm_modem_release_port (MMModem *self,
+ const char *subsys,
+ const char *name)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (MM_IS_MODEM (self));
+ g_return_if_fail (subsys);
+ g_return_if_fail (name);
+
+ g_assert (MM_MODEM_GET_INTERFACE (self)->release_port);
+ MM_MODEM_GET_INTERFACE (self)->release_port (self, subsys, name);
+}
+
+gboolean
+mm_modem_get_valid (MMModem *self)
+{
+ gboolean valid = FALSE;
+
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (MM_IS_MODEM (self), FALSE);
+
+ g_object_get (G_OBJECT (self), MM_MODEM_VALID, &valid, NULL);
+ return valid;
+}
+
+char *
+mm_modem_get_device (MMModem *self)
+{
+ char *device;
+
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (MM_IS_MODEM (self), NULL);
+
+ g_object_get (G_OBJECT (self), MM_MODEM_MASTER_DEVICE, &device, NULL);
+ return device;
+}
+
+MMModemState
+mm_modem_get_state (MMModem *self)
+{
+ MMModemState state = MM_MODEM_STATE_UNKNOWN;
+
+ g_object_get (G_OBJECT (self), MM_MODEM_STATE, &state, NULL);
+ return state;
+}
+
+static const char *
+state_to_string (MMModemState state)
+{
+ switch (state) {
+ case MM_MODEM_STATE_UNKNOWN:
+ return "unknown";
+ case MM_MODEM_STATE_DISABLED:
+ return "disabled";
+ case MM_MODEM_STATE_DISABLING:
+ return "disabling";
+ case MM_MODEM_STATE_ENABLING:
+ return "enabling";
+ case MM_MODEM_STATE_ENABLED:
+ return "enabled";
+ case MM_MODEM_STATE_SEARCHING:
+ return "searching";
+ case MM_MODEM_STATE_REGISTERED:
+ return "registered";
+ case MM_MODEM_STATE_DISCONNECTING:
+ return "disconnecting";
+ case MM_MODEM_STATE_CONNECTING:
+ return "connecting";
+ case MM_MODEM_STATE_CONNECTED:
+ return "connected";
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ g_assert_not_reached ();
+ return "(invalid)";
+}
+
+void
+mm_modem_set_state (MMModem *self,
+ MMModemState new_state,
+ MMModemStateReason reason)
+{
+ MMModemState old_state = MM_MODEM_STATE_UNKNOWN;
+ const char *dbus_path;
+
+ g_object_get (G_OBJECT (self), MM_MODEM_STATE, &old_state, NULL);
+
+ if (new_state != old_state) {
+ g_object_set (G_OBJECT (self), MM_MODEM_STATE, new_state, NULL);
+ g_signal_emit_by_name (G_OBJECT (self), "state-changed", new_state, old_state, reason);
+
+ dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG);
+ if (dbus_path) {
+ g_message ("Modem %s: state changed (%s -> %s)",
+ dbus_path,
+ state_to_string (old_state),
+ state_to_string (new_state));
+ }
+ }
+}
+
+/*****************************************************************************/
+
+static void
+mm_modem_init (gpointer g_iface)
+{
+ GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+ static gboolean initialized = FALSE;
+
+ if (initialized)
+ return;
+
+ /* Properties */
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_string (MM_MODEM_DATA_DEVICE,
+ "Device",
+ "Data device",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_string (MM_MODEM_MASTER_DEVICE,
+ "MasterDevice",
+ "Master modem parent device of all the modem's ports",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_string (MM_MODEM_DRIVER,
+ "Driver",
+ "Driver",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_string (MM_MODEM_PLUGIN,
+ "Plugin",
+ "Plugin name",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_uint (MM_MODEM_TYPE,
+ "Type",
+ "Type",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_uint (MM_MODEM_IP_METHOD,
+ "IP method",
+ "IP configuration method",
+ MM_MODEM_IP_METHOD_PPP,
+ MM_MODEM_IP_METHOD_DHCP,
+ MM_MODEM_IP_METHOD_PPP,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_boolean (MM_MODEM_VALID,
+ "Valid",
+ "Modem is valid",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_uint (MM_MODEM_STATE,
+ "State",
+ "State",
+ MM_MODEM_STATE_UNKNOWN,
+ MM_MODEM_STATE_LAST,
+ MM_MODEM_STATE_UNKNOWN,
+ G_PARAM_READWRITE));
+
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_boolean (MM_MODEM_ENABLED,
+ "Enabled",
+ "Modem is enabled",
+ FALSE,
+ G_PARAM_READABLE));
+
+ /* Signals */
+ g_signal_new ("state-changed",
+ iface_type,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMModem, state_changed),
+ NULL, NULL,
+ mm_marshal_VOID__UINT_UINT_UINT,
+ G_TYPE_NONE, 3,
+ G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
+
+ initialized = TRUE;
+}
+
+GType
+mm_modem_get_type (void)
+{
+ static GType modem_type = 0;
+
+ if (!G_UNLIKELY (modem_type)) {
+ const GTypeInfo modem_info = {
+ sizeof (MMModem), /* class_size */
+ mm_modem_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL
+ };
+
+ modem_type = g_type_register_static (G_TYPE_INTERFACE,
+ "MMModem",
+ &modem_info, 0);
+
+ g_type_interface_add_prerequisite (modem_type, G_TYPE_OBJECT);
+
+ dbus_g_object_type_install_info (modem_type, &dbus_glib_mm_modem_object_info);
+ }
+
+ return modem_type;
+}
diff --git a/src/mm-modem.h b/src/mm-modem.h
new file mode 100644
index 0000000..e8dd7ea
--- /dev/null
+++ b/src/mm-modem.h
@@ -0,0 +1,219 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_MODEM_H
+#define MM_MODEM_H
+
+#include <glib-object.h>
+
+#include "mm-port.h"
+
+typedef enum {
+ MM_MODEM_STATE_UNKNOWN = 0,
+ MM_MODEM_STATE_DISABLED = 10,
+ MM_MODEM_STATE_DISABLING = 20,
+ MM_MODEM_STATE_ENABLING = 30,
+ MM_MODEM_STATE_ENABLED = 40,
+ MM_MODEM_STATE_SEARCHING = 50,
+ MM_MODEM_STATE_REGISTERED = 60,
+ MM_MODEM_STATE_DISCONNECTING = 70,
+ MM_MODEM_STATE_CONNECTING = 80,
+ MM_MODEM_STATE_CONNECTED = 90,
+
+ MM_MODEM_STATE_LAST = MM_MODEM_STATE_CONNECTED
+} MMModemState;
+
+typedef enum {
+ MM_MODEM_STATE_REASON_NONE = 0
+} MMModemStateReason;
+
+#define DBUS_PATH_TAG "dbus-path"
+
+#define MM_TYPE_MODEM (mm_modem_get_type ())
+#define MM_MODEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM, MMModem))
+#define MM_IS_MODEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM))
+#define MM_MODEM_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM, MMModem))
+
+#define MM_MODEM_DBUS_INTERFACE "org.freedesktop.ModemManager.Modem"
+
+#define MM_MODEM_DATA_DEVICE "device"
+#define MM_MODEM_MASTER_DEVICE "master-device"
+#define MM_MODEM_DRIVER "driver"
+#define MM_MODEM_TYPE "type"
+#define MM_MODEM_IP_METHOD "ip-method"
+#define MM_MODEM_ENABLED "enabled"
+#define MM_MODEM_VALID "valid" /* not exported */
+#define MM_MODEM_PLUGIN "plugin" /* not exported */
+#define MM_MODEM_STATE "state" /* not exported */
+
+#define MM_MODEM_TYPE_UNKNOWN 0
+#define MM_MODEM_TYPE_GSM 1
+#define MM_MODEM_TYPE_CDMA 2
+
+#define MM_MODEM_IP_METHOD_PPP 0
+#define MM_MODEM_IP_METHOD_STATIC 1
+#define MM_MODEM_IP_METHOD_DHCP 2
+
+typedef enum {
+ MM_MODEM_PROP_FIRST = 0x1000,
+
+ MM_MODEM_PROP_DATA_DEVICE = MM_MODEM_PROP_FIRST,
+ MM_MODEM_PROP_MASTER_DEVICE,
+ MM_MODEM_PROP_DRIVER,
+ MM_MODEM_PROP_TYPE,
+ MM_MODEM_PROP_IP_METHOD,
+ MM_MODEM_PROP_VALID, /* Not exported */
+ MM_MODEM_PROP_PLUGIN, /* Not exported */
+ MM_MODEM_PROP_STATE, /* Not exported */
+ MM_MODEM_PROP_ENABLED
+} MMModemProp;
+
+typedef struct _MMModem MMModem;
+
+typedef void (*MMModemFn) (MMModem *modem,
+ GError *error,
+ gpointer user_data);
+
+typedef void (*MMModemUIntFn) (MMModem *modem,
+ guint32 result,
+ GError *error,
+ gpointer user_data);
+
+typedef void (*MMModemStringFn) (MMModem *modem,
+ const char *result,
+ GError *error,
+ gpointer user_data);
+
+typedef void (*MMModemIp4Fn) (MMModem *modem,
+ guint32 address,
+ GArray *dns,
+ GError *error,
+ gpointer user_data);
+
+typedef void (*MMModemInfoFn) (MMModem *modem,
+ const char *manufacturer,
+ const char *model,
+ const char *version,
+ GError *error,
+ gpointer user_data);
+
+struct _MMModem {
+ GTypeInterface g_iface;
+
+ /* Methods */
+ gboolean (*owns_port) (MMModem *self,
+ const char *subsys,
+ const char *name);
+
+ gboolean (*grab_port) (MMModem *self,
+ const char *subsys,
+ const char *name,
+ MMPortType suggested_type,
+ gpointer user_data,
+ GError **error);
+
+ void (*release_port) (MMModem *self,
+ const char *subsys,
+ const char *name);
+
+ void (*enable) (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*disable) (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*connect) (MMModem *self,
+ const char *number,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*get_ip4_config) (MMModem *self,
+ MMModemIp4Fn callback,
+ gpointer user_data);
+
+ void (*disconnect) (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data);
+
+ void (*get_info) (MMModem *self,
+ MMModemInfoFn callback,
+ gpointer user_data);
+
+ /* Signals */
+ void (*state_changed) (MMModem *self,
+ MMModemState new_state,
+ MMModemState old_state,
+ MMModemStateReason reason);
+};
+
+GType mm_modem_get_type (void);
+
+gboolean mm_modem_owns_port (MMModem *self,
+ const char *subsys,
+ const char *name);
+
+gboolean mm_modem_grab_port (MMModem *self,
+ const char *subsys,
+ const char *name,
+ MMPortType suggested_type,
+ gpointer user_data,
+ GError **error);
+
+void mm_modem_release_port (MMModem *self,
+ const char *subsys,
+ const char *name);
+
+void mm_modem_enable (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_disable (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_connect (MMModem *self,
+ const char *number,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_get_ip4_config (MMModem *self,
+ MMModemIp4Fn callback,
+ gpointer user_data);
+
+void mm_modem_disconnect (MMModem *self,
+ MMModemFn callback,
+ gpointer user_data);
+
+void mm_modem_get_info (MMModem *self,
+ MMModemInfoFn callback,
+ gpointer user_data);
+
+gboolean mm_modem_get_valid (MMModem *self);
+
+char *mm_modem_get_device (MMModem *self);
+
+MMModemState mm_modem_get_state (MMModem *self);
+
+void mm_modem_set_state (MMModem *self,
+ MMModemState new_state,
+ MMModemStateReason reason);
+
+GError *mm_modem_check_removed (MMModem *self, const GError *error);
+
+#endif /* MM_MODEM_H */
+
diff --git a/src/mm-options.c b/src/mm-options.c
new file mode 100644
index 0000000..7bbeefd
--- /dev/null
+++ b/src/mm-options.c
@@ -0,0 +1,55 @@
+/* -*- 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
new file mode 100644
index 0000000..ce33e27
--- /dev/null
+++ b/src/mm-options.h
@@ -0,0 +1,23 @@
+/* -*- 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
new file mode 100644
index 0000000..202eaac
--- /dev/null
+++ b/src/mm-plugin-base.c
@@ -0,0 +1,1126 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#define _GNU_SOURCE /* for strcasestr */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <string.h>
+
+#define G_UDEV_API_IS_SUBJECT_TO_CHANGE
+#include <gudev/gudev.h>
+
+#include "mm-plugin-base.h"
+#include "mm-serial-port.h"
+#include "mm-serial-parsers.h"
+#include "mm-errors.h"
+#include "mm-marshal.h"
+
+static void plugin_init (MMPlugin *plugin_class);
+
+G_DEFINE_TYPE_EXTENDED (MMPluginBase, mm_plugin_base, G_TYPE_OBJECT,
+ 0, G_IMPLEMENT_INTERFACE (MM_TYPE_PLUGIN, plugin_init))
+
+#define MM_PLUGIN_BASE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_PLUGIN_BASE, MMPluginBasePrivate))
+
+/* A hash table shared between all instances of the plugin base that
+ * caches the probed capabilities so that only one plugin has to actually
+ * probe a port.
+ */
+static GHashTable *cached_caps = NULL;
+
+
+typedef struct {
+ char *name;
+ GUdevClient *client;
+
+ GHashTable *modems;
+ GHashTable *tasks;
+} MMPluginBasePrivate;
+
+enum {
+ PROP_0,
+ PROP_NAME,
+ LAST_PROP
+};
+
+enum {
+ PROBE_RESULT,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+
+typedef enum {
+ PROBE_STATE_GCAP_TRY1 = 0,
+ PROBE_STATE_GCAP_TRY2,
+ PROBE_STATE_GCAP_TRY3,
+ PROBE_STATE_ATI,
+ PROBE_STATE_CPIN,
+ PROBE_STATE_CGMM,
+ PROBE_STATE_LAST
+} ProbeState;
+
+/*****************************************************************************/
+
+G_DEFINE_TYPE (MMPluginBaseSupportsTask, mm_plugin_base_supports_task, G_TYPE_OBJECT)
+
+#define MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, MMPluginBaseSupportsTaskPrivate))
+
+typedef struct {
+ MMPluginBase *plugin;
+ GUdevDevice *port;
+ GUdevDevice *physdev;
+ char *driver;
+
+ guint open_id;
+ guint32 open_tries;
+
+ MMSerialPort *probe_port;
+ guint32 probed_caps;
+ ProbeState probe_state;
+ guint probe_id;
+ char *probe_resp;
+ GError *probe_error;
+
+ char *custom_init;
+ guint32 custom_init_max_tries;
+ guint32 custom_init_tries;
+ guint32 custom_init_delay_seconds;
+ gboolean custom_init_fail_if_timeout;
+
+ MMSupportsPortResultFunc callback;
+ gpointer callback_data;
+} MMPluginBaseSupportsTaskPrivate;
+
+static MMPluginBaseSupportsTask *
+supports_task_new (MMPluginBase *self,
+ GUdevDevice *port,
+ GUdevDevice *physdev,
+ const char *driver,
+ MMSupportsPortResultFunc callback,
+ gpointer callback_data)
+{
+ MMPluginBaseSupportsTask *task;
+ MMPluginBaseSupportsTaskPrivate *priv;
+
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), NULL);
+ g_return_val_if_fail (port != NULL, NULL);
+ g_return_val_if_fail (physdev != NULL, NULL);
+ g_return_val_if_fail (driver != NULL, NULL);
+ g_return_val_if_fail (callback != NULL, NULL);
+
+ task = (MMPluginBaseSupportsTask *) g_object_new (MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, NULL);
+
+ priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+ priv->plugin = self;
+ priv->port = g_object_ref (port);
+ priv->physdev = g_object_ref (physdev);
+ priv->driver = g_strdup (driver);
+ priv->callback = callback;
+ priv->callback_data = callback_data;
+
+ return task;
+}
+
+MMPlugin *
+mm_plugin_base_supports_task_get_plugin (MMPluginBaseSupportsTask *task)
+{
+ g_return_val_if_fail (task != NULL, NULL);
+ g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), NULL);
+
+ return MM_PLUGIN (MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->plugin);
+}
+
+GUdevDevice *
+mm_plugin_base_supports_task_get_port (MMPluginBaseSupportsTask *task)
+{
+ g_return_val_if_fail (task != NULL, NULL);
+ g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), NULL);
+
+ return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->port;
+}
+
+GUdevDevice *
+mm_plugin_base_supports_task_get_physdev (MMPluginBaseSupportsTask *task)
+{
+ g_return_val_if_fail (task != NULL, NULL);
+ g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), NULL);
+
+ return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->physdev;
+}
+
+const char *
+mm_plugin_base_supports_task_get_driver (MMPluginBaseSupportsTask *task)
+{
+ g_return_val_if_fail (task != NULL, NULL);
+ g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), NULL);
+
+ return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->driver;
+}
+
+guint32
+mm_plugin_base_supports_task_get_probed_capabilities (MMPluginBaseSupportsTask *task)
+{
+ g_return_val_if_fail (task != NULL, 0);
+ g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), 0);
+
+ return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->probed_caps;
+}
+
+void
+mm_plugin_base_supports_task_complete (MMPluginBaseSupportsTask *task,
+ guint32 level)
+{
+ MMPluginBaseSupportsTaskPrivate *priv;
+ const char *subsys, *name;
+
+ g_return_if_fail (task != NULL);
+ g_return_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task));
+
+ priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+ g_return_if_fail (priv->callback != NULL);
+
+ subsys = g_udev_device_get_subsystem (priv->port);
+ name = g_udev_device_get_name (priv->port);
+
+ priv->callback (MM_PLUGIN (priv->plugin), subsys, name, level, priv->callback_data);
+
+ /* Clear out the callback, it shouldn't be called more than once */
+ priv->callback = NULL;
+ priv->callback_data = NULL;
+}
+
+void
+mm_plugin_base_supports_task_set_custom_init_command (MMPluginBaseSupportsTask *task,
+ const char *cmd,
+ guint32 delay_seconds,
+ guint32 max_tries,
+ gboolean fail_if_timeout)
+{
+ MMPluginBaseSupportsTaskPrivate *priv;
+
+ g_return_if_fail (task != NULL);
+ g_return_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task));
+
+ priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+
+ g_free (priv->custom_init);
+ priv->custom_init = g_strdup (cmd);
+ priv->custom_init_max_tries = max_tries;
+ priv->custom_init_delay_seconds = delay_seconds;
+ priv->custom_init_fail_if_timeout = fail_if_timeout;
+}
+
+static void
+mm_plugin_base_supports_task_init (MMPluginBaseSupportsTask *self)
+{
+}
+
+static void
+supports_task_dispose (GObject *object)
+{
+ MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (object);
+
+ if (MM_IS_SERIAL_PORT (priv->port))
+ mm_serial_port_flash_cancel (MM_SERIAL_PORT (priv->port));
+
+ g_object_unref (priv->port);
+ g_object_unref (priv->physdev);
+ g_free (priv->driver);
+ g_free (priv->probe_resp);
+ g_clear_error (&(priv->probe_error));
+ g_free (priv->custom_init);
+
+ if (priv->open_id)
+ g_source_remove (priv->open_id);
+
+ if (priv->probe_id)
+ g_source_remove (priv->probe_id);
+ if (priv->probe_port)
+ g_object_unref (priv->probe_port);
+
+ G_OBJECT_CLASS (mm_plugin_base_supports_task_parent_class)->dispose (object);
+}
+
+static void
+mm_plugin_base_supports_task_class_init (MMPluginBaseSupportsTaskClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMPluginBaseSupportsTaskPrivate));
+
+ /* Virtual methods */
+ object_class->dispose = supports_task_dispose;
+}
+
+/*****************************************************************************/
+
+#define MM_PLUGIN_BASE_PORT_CAP_CDMA (MM_PLUGIN_BASE_PORT_CAP_IS707_A | \
+ MM_PLUGIN_BASE_PORT_CAP_IS707_P | \
+ MM_PLUGIN_BASE_PORT_CAP_IS856 | \
+ MM_PLUGIN_BASE_PORT_CAP_IS856_A)
+
+#define CAP_GSM_OR_CDMA (MM_PLUGIN_BASE_PORT_CAP_CDMA | MM_PLUGIN_BASE_PORT_CAP_GSM)
+
+struct modem_caps {
+ char *name;
+ guint32 bits;
+};
+
+static struct modem_caps modem_caps[] = {
+ {"+CGSM", MM_PLUGIN_BASE_PORT_CAP_GSM},
+ {"+CIS707-A", MM_PLUGIN_BASE_PORT_CAP_IS707_A},
+ {"+CIS707A", MM_PLUGIN_BASE_PORT_CAP_IS707_A}, /* Cmotech */
+ {"+CIS707", MM_PLUGIN_BASE_PORT_CAP_IS707_A},
+ {"CIS707", MM_PLUGIN_BASE_PORT_CAP_IS707_A}, /* Qualcomm Gobi */
+ {"+CIS707P", MM_PLUGIN_BASE_PORT_CAP_IS707_P},
+ {"CIS-856", MM_PLUGIN_BASE_PORT_CAP_IS856},
+ {"+IS-856", MM_PLUGIN_BASE_PORT_CAP_IS856}, /* Cmotech */
+ {"CIS-856-A", MM_PLUGIN_BASE_PORT_CAP_IS856_A},
+ {"CIS-856A", MM_PLUGIN_BASE_PORT_CAP_IS856_A}, /* Kyocera KPC680 */
+ {"+DS", MM_PLUGIN_BASE_PORT_CAP_DS},
+ {"+ES", MM_PLUGIN_BASE_PORT_CAP_ES},
+ {"+MS", MM_PLUGIN_BASE_PORT_CAP_MS},
+ {"+FCLASS", MM_PLUGIN_BASE_PORT_CAP_FCLASS},
+ {NULL}
+};
+
+static guint32
+parse_gcap (const char *buf)
+{
+ struct modem_caps *cap = modem_caps;
+ guint32 ret = 0;
+
+ while (cap->name) {
+ if (strstr (buf, cap->name))
+ ret |= cap->bits;
+ cap++;
+ }
+ return ret;
+}
+
+static guint32
+parse_cpin (const char *buf)
+{
+ if ( strcasestr (buf, "SIM PIN")
+ || strcasestr (buf, "SIM PUK")
+ || strcasestr (buf, "PH-SIM PIN")
+ || strcasestr (buf, "PH-FSIM PIN")
+ || strcasestr (buf, "PH-FSIM PUK")
+ || strcasestr (buf, "SIM PIN2")
+ || strcasestr (buf, "SIM PUK2")
+ || strcasestr (buf, "PH-NET PIN")
+ || strcasestr (buf, "PH-NET PUK")
+ || strcasestr (buf, "PH-NETSUB PIN")
+ || strcasestr (buf, "PH-NETSUB PUK")
+ || strcasestr (buf, "PH-SP PIN")
+ || strcasestr (buf, "PH-SP PUK")
+ || strcasestr (buf, "PH-CORP PIN")
+ || strcasestr (buf, "PH-CORP PUK"))
+ return MM_PLUGIN_BASE_PORT_CAP_GSM;
+
+ return 0;
+}
+
+static guint32
+parse_cgmm (const char *buf)
+{
+ if (strstr (buf, "GSM900") || strstr (buf, "GSM1800") ||
+ strstr (buf, "GSM1900") || strstr (buf, "GSM850"))
+ return MM_PLUGIN_BASE_PORT_CAP_GSM;
+ return 0;
+}
+
+static gboolean
+emit_probe_result (gpointer user_data)
+{
+ MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data);
+ MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+ MMPlugin *self = mm_plugin_base_supports_task_get_plugin (task);
+
+ /* Close the serial port */
+ g_object_unref (task_priv->probe_port);
+ task_priv->probe_port = NULL;
+
+ task_priv->probe_id = 0;
+ g_signal_emit (self, signals[PROBE_RESULT], 0, task, task_priv->probed_caps);
+ return FALSE;
+}
+
+static void
+probe_complete (MMPluginBaseSupportsTask *task)
+{
+ MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+
+ g_hash_table_insert (cached_caps,
+ g_strdup (mm_port_get_device (MM_PORT (task_priv->probe_port))),
+ GUINT_TO_POINTER (task_priv->probed_caps));
+
+ task_priv->probe_id = g_idle_add (emit_probe_result, task);
+}
+
+static void
+parse_response (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data);
+
+static void
+real_handle_probe_response (MMPluginBase *self,
+ MMPluginBaseSupportsTask *task,
+ const char *cmd,
+ const char *response,
+ const GError *error)
+{
+ MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+ MMSerialPort *port = task_priv->probe_port;
+ gboolean ignore_error = FALSE;
+
+ /* Some modems (Huawei E160g) won't respond to +GCAP with no SIM, but
+ * will respond to ATI.
+ */
+ if (response && strstr (response, "+CME ERROR:"))
+ ignore_error = TRUE;
+
+ if (error && !ignore_error) {
+ if (error->code == MM_SERIAL_RESPONSE_TIMEOUT) {
+ /* Try GCAP again */
+ if (task_priv->probe_state < PROBE_STATE_GCAP_TRY3) {
+ task_priv->probe_state++;
+ mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, task);
+ } else {
+ /* Otherwise, if all the GCAP tries timed out, ignore the port
+ * as it's probably not an AT-capable port.
+ */
+ probe_complete (task);
+ }
+ return;
+ }
+
+ /* Otherwise proceed to the next command */
+ } else if (response) {
+ /* Parse the response */
+
+ switch (task_priv->probe_state) {
+ case PROBE_STATE_GCAP_TRY1:
+ case PROBE_STATE_GCAP_TRY2:
+ case PROBE_STATE_GCAP_TRY3:
+ case PROBE_STATE_ATI:
+ /* Some modems don't respond to AT+GCAP, but often they put a
+ * GCAP-style response as a line in the ATI response.
+ */
+ task_priv->probed_caps = parse_gcap (response);
+ break;
+ case PROBE_STATE_CPIN:
+ /* Some devices (ZTE MF628/ONDA MT503HS for example) reply to
+ * anything but AT+CPIN? with ERROR if the device has a PIN set.
+ * Since no known CDMA modems support AT+CPIN? we can consider the
+ * device a GSM device if it returns a non-error response to AT+CPIN?.
+ */
+ task_priv->probed_caps = parse_cpin (response);
+ break;
+ case PROBE_STATE_CGMM:
+ /* Some models (BUSlink SCWi275u) stick stupid stuff in the CGMM
+ * response but at least it allows us to identify them.
+ */
+ task_priv->probed_caps = parse_cgmm (response);
+ break;
+ default:
+ break;
+ }
+
+ if (task_priv->probed_caps & CAP_GSM_OR_CDMA) {
+ probe_complete (task);
+ return;
+ }
+ }
+
+ task_priv->probe_state++;
+
+ /* Try a different command */
+ switch (task_priv->probe_state) {
+ case PROBE_STATE_GCAP_TRY2:
+ case PROBE_STATE_GCAP_TRY3:
+ mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, task);
+ break;
+ case PROBE_STATE_ATI:
+ /* After the last GCAP attempt, try ATI */
+ mm_serial_port_queue_command (port, "I", 3, parse_response, task);
+ break;
+ case PROBE_STATE_CPIN:
+ /* After the ATI attempt, try CPIN */
+ mm_serial_port_queue_command (port, "+CPIN?", 3, parse_response, task);
+ break;
+ case PROBE_STATE_CGMM:
+ /* After the CPIN attempt, try CGMM */
+ mm_serial_port_queue_command (port, "+CGMM", 3, parse_response, task);
+ break;
+ default:
+ /* Probably not GSM or CDMA */
+ probe_complete (task);
+ break;
+ }
+}
+
+static gboolean
+handle_probe_response (gpointer user_data)
+{
+ MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data);
+ MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+ MMPluginBase *self = MM_PLUGIN_BASE (mm_plugin_base_supports_task_get_plugin (task));
+ const char *cmd = NULL;
+
+ switch (task_priv->probe_state) {
+ case PROBE_STATE_GCAP_TRY1:
+ case PROBE_STATE_GCAP_TRY2:
+ case PROBE_STATE_GCAP_TRY3:
+ cmd = "+GCAP";
+ break;
+ case PROBE_STATE_ATI:
+ cmd = "I";
+ break;
+ case PROBE_STATE_CPIN:
+ cmd = "+CPIN?";
+ break;
+ case PROBE_STATE_CGMM:
+ default:
+ cmd = "+CGMM";
+ break;
+ }
+
+ MM_PLUGIN_BASE_GET_CLASS (self)->handle_probe_response (self,
+ task,
+ cmd,
+ task_priv->probe_resp,
+ task_priv->probe_error);
+ return FALSE;
+}
+
+static void
+parse_response (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data);
+ MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+
+ if (task_priv->probe_id)
+ g_source_remove (task_priv->probe_id);
+ g_free (task_priv->probe_resp);
+ task_priv->probe_resp = NULL;
+ g_clear_error (&(task_priv->probe_error));
+
+ if (response && response->len)
+ task_priv->probe_resp = g_strdup (response->str);
+ if (error)
+ task_priv->probe_error = g_error_copy (error);
+
+ /* Schedule the response handler in an idle, since we can't emit the
+ * PROBE_RESULT signal from the serial port response handler without
+ * potentially destroying the serial port in the middle of its response
+ * handler, which it understandably doesn't like.
+ */
+ task_priv->probe_id = g_idle_add (handle_probe_response, task);
+}
+
+static void flash_done (MMSerialPort *port, GError *error, gpointer user_data);
+
+static void
+custom_init_response (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data);
+ MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+
+ if (error) {
+ task_priv->custom_init_tries++;
+ if (task_priv->custom_init_tries < task_priv->custom_init_max_tries) {
+ /* Try the custom command again */
+ flash_done (port, NULL, user_data);
+ return;
+ } else if (task_priv->custom_init_fail_if_timeout) {
+ /* Fail the probe if the plugin wanted it and the command timed out */
+ if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_RESPONSE_TIMEOUT)) {
+ probe_complete (task);
+ return;
+ }
+ }
+ }
+
+ /* Otherwise proceed to probing */
+ mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, user_data);
+}
+
+static void
+flash_done (MMSerialPort *port, GError *error, gpointer user_data)
+{
+ MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data);
+ MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+ guint32 delay_secs = task_priv->custom_init_delay_seconds;
+
+ /* Send the custom init command if any */
+ if (task_priv->custom_init) {
+ if (!delay_secs)
+ delay_secs = 3;
+ mm_serial_port_queue_command (port,
+ task_priv->custom_init,
+ delay_secs,
+ custom_init_response,
+ user_data);
+ } else {
+ /* Otherwise start normal probing */
+ custom_init_response (port, NULL, NULL, user_data);
+ }
+}
+
+static gboolean
+try_open (gpointer user_data)
+{
+ MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data);
+ MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+ GError *error = NULL;
+
+ task_priv->open_id = 0;
+
+ if (!mm_serial_port_open (task_priv->probe_port, &error)) {
+ if (++task_priv->open_tries > 4) {
+ /* took too long to open the port; give up */
+ g_warning ("(%s): failed to open after 4 tries.",
+ mm_port_get_device (MM_PORT (task_priv->probe_port)));
+ probe_complete (task);
+ } else if (g_error_matches (error,
+ MM_SERIAL_ERROR,
+ MM_SERIAL_OPEN_FAILED_NO_DEVICE)) {
+ /* this is nozomi being dumb; try again */
+ task_priv->open_id = g_timeout_add_seconds (1, try_open, task);
+ } else {
+ /* some other hard error */
+ probe_complete (task);
+ }
+ g_clear_error (&error);
+ } else {
+ /* success, start probing */
+ GUdevDevice *port;
+
+ port = mm_plugin_base_supports_task_get_port (task);
+ g_assert (port);
+
+ g_debug ("(%s): probe requested by plugin '%s'",
+ g_udev_device_get_name (port),
+ mm_plugin_get_name (MM_PLUGIN (task_priv->plugin)));
+ mm_serial_port_flash (task_priv->probe_port, 100, flash_done, task);
+ }
+
+ return FALSE;
+}
+
+gboolean
+mm_plugin_base_probe_port (MMPluginBase *self,
+ MMPluginBaseSupportsTask *task,
+ GError **error)
+{
+ MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
+ MMSerialPort *serial;
+ const char *name;
+ GUdevDevice *port;
+
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), FALSE);
+ g_return_val_if_fail (task != NULL, FALSE);
+
+ port = mm_plugin_base_supports_task_get_port (task);
+ g_assert (port);
+ name = g_udev_device_get_name (port);
+ g_assert (name);
+
+ serial = mm_serial_port_new (name, MM_PORT_TYPE_PRIMARY);
+ if (serial == NULL) {
+ g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Failed to create new serial port.");
+ return FALSE;
+ }
+
+ g_object_set (serial,
+ MM_SERIAL_PORT_SEND_DELAY, (guint64) 100000,
+ MM_PORT_CARRIER_DETECT, FALSE,
+ NULL);
+
+ mm_serial_port_set_response_parser (serial,
+ mm_serial_parser_v1_parse,
+ mm_serial_parser_v1_new (),
+ mm_serial_parser_v1_destroy);
+
+ /* Open the port */
+ task_priv->probe_port = serial;
+ task_priv->open_id = g_idle_add (try_open, task);
+ return TRUE;
+}
+
+gboolean
+mm_plugin_base_get_cached_port_capabilities (MMPluginBase *self,
+ GUdevDevice *port,
+ guint32 *capabilities)
+{
+ gpointer tmp = NULL;
+ gboolean found;
+
+ found = g_hash_table_lookup_extended (cached_caps, g_udev_device_get_name (port), NULL, tmp);
+ *capabilities = GPOINTER_TO_UINT (tmp);
+ return found;
+}
+
+/*****************************************************************************/
+
+static void
+modem_destroyed (gpointer data, GObject *modem)
+{
+ MMPluginBase *self = MM_PLUGIN_BASE (data);
+ MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ /* Remove it from the modems info */
+ g_hash_table_iter_init (&iter, priv->modems);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ if (value == modem) {
+ g_hash_table_iter_remove (&iter);
+ break;
+ }
+ }
+
+ /* Since we don't track port cached capabilities on a per-modem basis,
+ * we just have to live with blowing away the cached capabilities whenever
+ * a modem gets removed. Could do better here by storing a structure
+ * in the cached capabilities table that includes { caps, modem device }
+ * or something and then only removing cached capabilities for ports
+ * that the modem that was just removed owned, but whatever.
+ */
+ g_hash_table_remove_all (cached_caps);
+}
+
+MMModem *
+mm_plugin_base_find_modem (MMPluginBase *self,
+ const char *master_device)
+{
+ MMPluginBasePrivate *priv;
+
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), NULL);
+ g_return_val_if_fail (master_device != NULL, NULL);
+ g_return_val_if_fail (strlen (master_device) > 0, NULL);
+
+ priv = MM_PLUGIN_BASE_GET_PRIVATE (self);
+ return g_hash_table_lookup (priv->modems, master_device);
+}
+
+/* From hostap, Copyright (c) 2002-2005, Jouni Malinen <jkmaline@cc.hut.fi> */
+
+static int hex2num (char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return -1;
+}
+
+static int hex2byte (const char *hex)
+{
+ int a, b;
+ a = hex2num(*hex++);
+ if (a < 0)
+ return -1;
+ b = hex2num(*hex++);
+ if (b < 0)
+ return -1;
+ return (a << 4) | b;
+}
+
+/* End from hostap */
+
+gboolean
+mm_plugin_base_get_device_ids (MMPluginBase *self,
+ const char *subsys,
+ const char *name,
+ guint16 *vendor,
+ guint16 *product)
+{
+ MMPluginBasePrivate *priv;
+ GUdevDevice *device = NULL;
+ const char *vid, *pid;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), FALSE);
+ g_return_val_if_fail (subsys != NULL, FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+ if (vendor)
+ g_return_val_if_fail (*vendor == 0, FALSE);
+ if (product)
+ g_return_val_if_fail (*product == 0, FALSE);
+
+ priv = MM_PLUGIN_BASE_GET_PRIVATE (self);
+
+ device = g_udev_client_query_by_subsystem_and_name (priv->client, subsys, name);
+ if (!device)
+ goto out;
+
+ vid = g_udev_device_get_property (device, "ID_VENDOR_ID");
+ if (!vid || (strlen (vid) != 4))
+ goto out;
+
+ if (vendor) {
+ *vendor = (guint16) (hex2byte (vid + 2) & 0xFF);
+ *vendor |= (guint16) ((hex2byte (vid) & 0xFF) << 8);
+ }
+
+ pid = g_udev_device_get_property (device, "ID_MODEL_ID");
+ if (!pid || (strlen (pid) != 4)) {
+ *vendor = 0;
+ goto out;
+ }
+
+ if (product) {
+ *product = (guint16) (hex2byte (pid + 2) & 0xFF);
+ *product |= (guint16) ((hex2byte (pid) & 0xFF) << 8);
+ }
+
+ success = TRUE;
+
+out:
+ if (device)
+ g_object_unref (device);
+ return success;
+}
+
+static char *
+get_key (const char *subsys, const char *name)
+{
+ return g_strdup_printf ("%s%s", subsys, name);
+}
+
+static const char *
+get_name (MMPlugin *plugin)
+{
+ return MM_PLUGIN_BASE_GET_PRIVATE (plugin)->name;
+}
+
+static char *
+get_driver_name (GUdevDevice *device)
+{
+ GUdevDevice *parent = NULL;
+ const char *driver, *subsys;
+ char *ret = NULL;
+
+ driver = g_udev_device_get_driver (device);
+ if (!driver) {
+ parent = g_udev_device_get_parent (device);
+ if (parent)
+ driver = g_udev_device_get_driver (parent);
+
+ /* Check for bluetooth; it's driver is a bunch of levels up so we
+ * just check for the subsystem of the parent being bluetooth.
+ */
+ if (!driver && parent) {
+ subsys = g_udev_device_get_subsystem (parent);
+ if (subsys && !strcmp (subsys, "bluetooth"))
+ driver = "bluetooth";
+ }
+ }
+
+ if (driver)
+ ret = g_strdup (driver);
+ if (parent)
+ g_object_unref (parent);
+
+ return ret;
+}
+
+static GUdevDevice *
+real_find_physical_device (MMPluginBase *plugin, GUdevDevice *child)
+{
+ GUdevDevice *iter, *old = NULL;
+ GUdevDevice *physdev = NULL;
+ const char *subsys, *type;
+ guint32 i = 0;
+ gboolean is_usb = FALSE, is_pci = FALSE;
+
+ g_return_val_if_fail (child != NULL, NULL);
+
+ iter = g_object_ref (child);
+ while (iter && i++ < 8) {
+ subsys = g_udev_device_get_subsystem (iter);
+ if (subsys) {
+ if (is_usb || !strcmp (subsys, "usb")) {
+ is_usb = TRUE;
+ type = g_udev_device_get_devtype (iter);
+ if (type && !strcmp (type, "usb_device")) {
+ physdev = iter;
+ break;
+ }
+ } else if (is_pci || !strcmp (subsys, "pci")) {
+ is_pci = TRUE;
+ physdev = iter;
+ break;
+ }
+ }
+
+ old = iter;
+ iter = g_udev_device_get_parent (old);
+ g_object_unref (old);
+ }
+
+ return physdev;
+}
+
+static MMPluginSupportsResult
+supports_port (MMPlugin *plugin,
+ const char *subsys,
+ const char *name,
+ MMSupportsPortResultFunc callback,
+ gpointer callback_data)
+{
+ MMPluginBase *self = MM_PLUGIN_BASE (plugin);
+ MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self);
+ GUdevDevice *port = NULL, *physdev = NULL;
+ char *driver = NULL, *key = NULL;
+ MMPluginBaseSupportsTask *task;
+ MMPluginSupportsResult result = MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
+ MMModem *existing;
+ const char *master_path;
+
+ key = get_key (subsys, name);
+ task = g_hash_table_lookup (priv->tasks, key);
+ if (task) {
+ g_free (key);
+ g_return_val_if_fail (task == NULL, MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED);
+ }
+
+ port = g_udev_client_query_by_subsystem_and_name (priv->client, subsys, name);
+ if (!port)
+ goto out;
+
+ physdev = MM_PLUGIN_BASE_GET_CLASS (self)->find_physical_device (self, port);
+ if (!physdev)
+ goto out;
+
+ driver = get_driver_name (port);
+ if (!driver)
+ goto out;
+
+ task = supports_task_new (self, port, physdev, driver, callback, callback_data);
+ g_assert (task);
+ g_hash_table_insert (priv->tasks, g_strdup (key), g_object_ref (task));
+
+ /* Help the plugin out a bit by finding an existing modem for this port */
+ master_path = g_udev_device_get_sysfs_path (physdev);
+ existing = g_hash_table_lookup (priv->modems, master_path);
+
+ 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
+ * not needed.
+ */
+ g_hash_table_remove (priv->tasks, key);
+ }
+ g_object_unref (task);
+
+out:
+ if (physdev)
+ g_object_unref (physdev);
+ if (port)
+ g_object_unref (port);
+ g_free (key);
+ g_free (driver);
+ return result;
+}
+
+static void
+cancel_supports_port (MMPlugin *plugin,
+ const char *subsys,
+ const char *name)
+{
+ MMPluginBase *self = MM_PLUGIN_BASE (plugin);
+ MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self);
+ MMPluginBaseSupportsTask *task;
+ char *key;
+
+ key = get_key (subsys, name);
+ task = g_hash_table_lookup (priv->tasks, key);
+ if (task) {
+ /* Let the plugin cancel any ongoing tasks */
+ if (MM_PLUGIN_BASE_GET_CLASS (self)->cancel_task)
+ MM_PLUGIN_BASE_GET_CLASS (self)->cancel_task (self, task);
+ g_hash_table_remove (priv->tasks, key);
+ }
+
+ g_free (key);
+}
+
+static MMModem *
+grab_port (MMPlugin *plugin,
+ const char *subsys,
+ const char *name,
+ GError **error)
+{
+ MMPluginBase *self = MM_PLUGIN_BASE (plugin);
+ MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self);
+ MMPluginBaseSupportsTask *task;
+ char *key;
+ MMModem *existing = NULL, *modem = NULL;
+ const char *master_path;
+
+ key = get_key (subsys, name);
+ task = g_hash_table_lookup (priv->tasks, key);
+ if (!task) {
+ g_free (key);
+ g_return_val_if_fail (task != NULL, FALSE);
+ }
+
+ /* Help the plugin out a bit by finding an existing modem for this port */
+ master_path = g_udev_device_get_sysfs_path (mm_plugin_base_supports_task_get_physdev (task));
+ existing = g_hash_table_lookup (priv->modems, master_path);
+
+ /* Let the modem grab the port */
+ modem = MM_PLUGIN_BASE_GET_CLASS (self)->grab_port (self, existing, task, error);
+ if (modem && !existing) {
+ g_hash_table_insert (priv->modems, g_strdup (master_path), modem);
+ g_object_weak_ref (G_OBJECT (modem), modem_destroyed, self);
+ }
+
+ g_hash_table_remove (priv->tasks, key);
+ g_free (key);
+ return modem;
+}
+
+/*****************************************************************************/
+
+static void
+plugin_init (MMPlugin *plugin_class)
+{
+ /* interface implementation */
+ plugin_class->get_name = get_name;
+ plugin_class->supports_port = supports_port;
+ plugin_class->cancel_supports_port = cancel_supports_port;
+ plugin_class->grab_port = grab_port;
+}
+
+static void
+mm_plugin_base_init (MMPluginBase *self)
+{
+ MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self);
+ const char *subsys[] = { "tty", "net", NULL };
+
+ if (!cached_caps)
+ cached_caps = g_hash_table_new (g_str_hash, g_str_equal);
+
+ priv->client = g_udev_client_new (subsys);
+
+ priv->modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) g_object_unref);
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ /* Construct only */
+ priv->name = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ g_value_set_string (value, priv->name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (object);
+
+ g_free (priv->name);
+
+ g_object_unref (priv->client);
+
+ g_hash_table_destroy (priv->modems);
+ g_hash_table_destroy (priv->tasks);
+
+ G_OBJECT_CLASS (mm_plugin_base_parent_class)->finalize (object);
+}
+
+static void
+mm_plugin_base_class_init (MMPluginBaseClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMPluginBasePrivate));
+
+ klass->find_physical_device = real_find_physical_device;
+ klass->handle_probe_response = real_handle_probe_response;
+
+ /* Virtual methods */
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->finalize = finalize;
+
+ g_object_class_install_property
+ (object_class, PROP_NAME,
+ g_param_spec_string (MM_PLUGIN_BASE_NAME,
+ "Name",
+ "Name",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ signals[PROBE_RESULT] =
+ g_signal_new ("probe-result",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMPluginBaseClass, probe_result),
+ NULL, NULL,
+ mm_marshal_VOID__OBJECT_UINT,
+ G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_UINT);
+}
diff --git a/src/mm-plugin-base.h b/src/mm-plugin-base.h
new file mode 100644
index 0000000..49e68d4
--- /dev/null
+++ b/src/mm-plugin-base.h
@@ -0,0 +1,150 @@
+/* -*- 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) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_PLUGIN_BASE_H
+#define MM_PLUGIN_BASE_H
+
+#include <glib.h>
+#include <glib/gtypes.h>
+#include <glib-object.h>
+
+#define G_UDEV_API_IS_SUBJECT_TO_CHANGE
+#include <gudev/gudev.h>
+
+#include "mm-plugin.h"
+#include "mm-modem.h"
+#include "mm-port.h"
+
+#define MM_PLUGIN_BASE_PORT_CAP_GSM 0x0001 /* GSM */
+#define MM_PLUGIN_BASE_PORT_CAP_IS707_A 0x0002 /* CDMA Circuit Switched Data */
+#define MM_PLUGIN_BASE_PORT_CAP_IS707_P 0x0004 /* CDMA Packet Switched Data */
+#define MM_PLUGIN_BASE_PORT_CAP_DS 0x0008 /* Data compression selection (v.42bis) */
+#define MM_PLUGIN_BASE_PORT_CAP_ES 0x0010 /* Error control selection (v.42) */
+#define MM_PLUGIN_BASE_PORT_CAP_FCLASS 0x0020 /* Group III Fax */
+#define MM_PLUGIN_BASE_PORT_CAP_MS 0x0040 /* Modulation selection */
+#define MM_PLUGIN_BASE_PORT_CAP_W 0x0080 /* Wireless commands */
+#define MM_PLUGIN_BASE_PORT_CAP_IS856 0x0100 /* CDMA 3G EVDO rev 0 */
+#define MM_PLUGIN_BASE_PORT_CAP_IS856_A 0x0200 /* CDMA 3G EVDO rev A */
+
+#define MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK (mm_plugin_base_supports_task_get_type ())
+#define MM_PLUGIN_BASE_SUPPORTS_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, MMPluginBaseSupportsTask))
+#define MM_PLUGIN_BASE_SUPPORTS_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, MMPluginBaseSupportsTaskClass))
+#define MM_IS_PLUGIN_BASE_SUPPORTS_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK))
+#define MM_IS_PLUBIN_BASE_SUPPORTS_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK))
+#define MM_PLUGIN_BASE_SUPPORTS_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, MMPluginBaseSupportsTaskClass))
+
+typedef struct {
+ GObject parent;
+} MMPluginBaseSupportsTask;
+
+typedef struct {
+ GObjectClass parent;
+} MMPluginBaseSupportsTaskClass;
+
+GType mm_plugin_base_supports_task_get_type (void);
+
+MMPlugin *mm_plugin_base_supports_task_get_plugin (MMPluginBaseSupportsTask *task);
+
+GUdevDevice *mm_plugin_base_supports_task_get_port (MMPluginBaseSupportsTask *task);
+
+GUdevDevice *mm_plugin_base_supports_task_get_physdev (MMPluginBaseSupportsTask *task);
+
+const char *mm_plugin_base_supports_task_get_driver (MMPluginBaseSupportsTask *task);
+
+guint32 mm_plugin_base_supports_task_get_probed_capabilities (MMPluginBaseSupportsTask *task);
+
+void mm_plugin_base_supports_task_complete (MMPluginBaseSupportsTask *task,
+ guint32 level);
+
+void mm_plugin_base_supports_task_set_custom_init_command (MMPluginBaseSupportsTask *task,
+ const char *cmd,
+ guint32 delay_seconds,
+ guint32 max_tries,
+ gboolean fail_if_timeout);
+
+#define MM_TYPE_PLUGIN_BASE (mm_plugin_base_get_type ())
+#define MM_PLUGIN_BASE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_BASE, MMPluginBase))
+#define MM_PLUGIN_BASE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_BASE, MMPluginBaseClass))
+#define MM_IS_PLUGIN_BASE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_BASE))
+#define MM_IS_PLUBIN_BASE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_BASE))
+#define MM_PLUGIN_BASE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_BASE, MMPluginBaseClass))
+
+#define MM_PLUGIN_BASE_NAME "name"
+
+typedef struct _MMPluginBase MMPluginBase;
+typedef struct _MMPluginBaseClass MMPluginBaseClass;
+
+struct _MMPluginBase {
+ GObject parent;
+};
+
+struct _MMPluginBaseClass {
+ GObjectClass parent;
+
+ /* Mandatory subclass functions */
+ MMPluginSupportsResult (*supports_port) (MMPluginBase *plugin,
+ MMModem *existing,
+ MMPluginBaseSupportsTask *task);
+
+ MMModem *(*grab_port) (MMPluginBase *plugin,
+ MMModem *existing,
+ MMPluginBaseSupportsTask *task,
+ GError **error);
+
+ /* Optional subclass functions */
+ void (*cancel_task) (MMPluginBase *plugin,
+ MMPluginBaseSupportsTask *task);
+
+ /* Find a the physical device of a port, ie the USB or PCI or whatever
+ * "master" device that owns the port. The GUdevDevice object returned
+ * will be unref-ed by the caller.
+ */
+ GUdevDevice * (*find_physical_device) (MMPluginBase *plugin,
+ GUdevDevice *port);
+
+ void (*handle_probe_response) (MMPluginBase *plugin,
+ MMPluginBaseSupportsTask *task,
+ const char *command,
+ const char *response,
+ const GError *error);
+
+ /* Signals */
+ void (*probe_result) (MMPluginBase *self,
+ MMPluginBaseSupportsTask *task,
+ guint32 capabilities);
+};
+
+GType mm_plugin_base_get_type (void);
+
+MMModem *mm_plugin_base_find_modem (MMPluginBase *self,
+ const char *master_device);
+
+gboolean mm_plugin_base_get_device_ids (MMPluginBase *self,
+ const char *subsys,
+ const char *name,
+ guint16 *vendor,
+ guint16 *product);
+
+gboolean mm_plugin_base_probe_port (MMPluginBase *self,
+ MMPluginBaseSupportsTask *task,
+ GError **error);
+
+/* Returns TRUE if the port was previously probed, FALSE if not */
+gboolean mm_plugin_base_get_cached_port_capabilities (MMPluginBase *self,
+ GUdevDevice *port,
+ guint32 *capabilities);
+
+#endif /* MM_PLUGIN_BASE_H */
+
diff --git a/src/mm-plugin.c b/src/mm-plugin.c
new file mode 100644
index 0000000..830f2d6
--- /dev/null
+++ b/src/mm-plugin.c
@@ -0,0 +1,100 @@
+/* -*- 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.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include "mm-plugin.h"
+
+const char *
+mm_plugin_get_name (MMPlugin *plugin)
+{
+ g_return_val_if_fail (MM_IS_PLUGIN (plugin), NULL);
+
+ return MM_PLUGIN_GET_INTERFACE (plugin)->get_name (plugin);
+}
+
+MMPluginSupportsResult
+mm_plugin_supports_port (MMPlugin *plugin,
+ const char *subsys,
+ const char *name,
+ MMSupportsPortResultFunc callback,
+ gpointer user_data)
+{
+ g_return_val_if_fail (MM_IS_PLUGIN (plugin), FALSE);
+ g_return_val_if_fail (subsys != NULL, FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+ g_return_val_if_fail (callback != NULL, FALSE);
+
+ return MM_PLUGIN_GET_INTERFACE (plugin)->supports_port (plugin, subsys, name, callback, user_data);
+}
+
+void
+mm_plugin_cancel_supports_port (MMPlugin *plugin,
+ const char *subsys,
+ const char *name)
+{
+ g_return_if_fail (MM_IS_PLUGIN (plugin));
+ g_return_if_fail (subsys != NULL);
+ g_return_if_fail (name != NULL);
+
+ MM_PLUGIN_GET_INTERFACE (plugin)->cancel_supports_port (plugin, subsys, name);
+}
+
+MMModem *
+mm_plugin_grab_port (MMPlugin *plugin,
+ const char *subsys,
+ const char *name,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_PLUGIN (plugin), FALSE);
+ g_return_val_if_fail (subsys != NULL, FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ return MM_PLUGIN_GET_INTERFACE (plugin)->grab_port (plugin, subsys, name, error);
+}
+
+/*****************************************************************************/
+
+static void
+mm_plugin_init (gpointer g_iface)
+{
+}
+
+GType
+mm_plugin_get_type (void)
+{
+ static GType plugin_type = 0;
+
+ if (!G_UNLIKELY (plugin_type)) {
+ const GTypeInfo plugin_info = {
+ sizeof (MMPlugin), /* class_size */
+ mm_plugin_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL
+ };
+
+ plugin_type = g_type_register_static (G_TYPE_INTERFACE,
+ "MMPlugin",
+ &plugin_info, 0);
+
+ g_type_interface_add_prerequisite (plugin_type, G_TYPE_OBJECT);
+ }
+
+ return plugin_type;
+}
diff --git a/src/mm-plugin.h b/src/mm-plugin.h
new file mode 100644
index 0000000..d1e85b6
--- /dev/null
+++ b/src/mm-plugin.h
@@ -0,0 +1,120 @@
+/* -*- 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.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_PLUGIN_H
+#define MM_PLUGIN_H
+
+#include <glib-object.h>
+#include <mm-modem.h>
+
+#define MM_PLUGIN_GENERIC_NAME "Generic"
+
+#define MM_PLUGIN_MAJOR_VERSION 3
+#define MM_PLUGIN_MINOR_VERSION 0
+
+#define MM_TYPE_PLUGIN (mm_plugin_get_type ())
+#define MM_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN, MMPlugin))
+#define MM_IS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN))
+#define MM_PLUGIN_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_PLUGIN, MMPlugin))
+
+typedef struct _MMPlugin MMPlugin;
+
+typedef MMPlugin *(*MMPluginCreateFunc) (void);
+
+/*
+ * 'level' is a value between 0 and 100 inclusive, where 0 means the plugin has
+ * no support for the port, and 100 means the plugin has full support for the
+ * port.
+ */
+typedef void (*MMSupportsPortResultFunc) (MMPlugin *plugin,
+ const char *subsys,
+ const char *name,
+ guint32 level,
+ gpointer user_data);
+
+typedef enum {
+ MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED = 0x0,
+ MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS,
+ MM_PLUGIN_SUPPORTS_PORT_DEFER
+} MMPluginSupportsResult;
+
+struct _MMPlugin {
+ GTypeInterface g_iface;
+
+ /* Methods */
+ const char *(*get_name) (MMPlugin *self);
+
+ /* Check whether a plugin supports a particular modem port, and what level
+ * of support the plugin has for the device. If the plugin can immediately
+ * determine whether a port is unsupported, it should return
+ * FALSE. Otherwise, if the plugin needs to perform additional operations
+ * (ie, probing) to determine the level of support or additional details
+ * about a port, it should queue that operation (but *not* block on the
+ * result) and return TRUE to indicate the operation is ongoing. When the
+ * operation is finished or the level of support is known, the plugin should
+ * call the provided callback and pass that callback the provided user_data.
+ */
+ MMPluginSupportsResult (*supports_port) (MMPlugin *self,
+ const char *subsys,
+ const char *name,
+ MMSupportsPortResultFunc callback,
+ gpointer user_data);
+
+ /* Called to cancel an ongoing supports_port() operation or to notify the
+ * plugin to clean up that operation. For example, if two plugins support
+ * a particular port, but the first plugin grabs the port, this method will
+ * be called on the second plugin to allow that plugin to clean up resources
+ * used while determining it's level of support for the port.
+ */
+ void (*cancel_supports_port) (MMPlugin *self,
+ const char *subsys,
+ const char *name);
+
+ /* Will only be called if the plugin returns a value greater than 0 for
+ * the supports_port() method for this port. The plugin should create and
+ * return a new modem for the port's device if there is no existing modem
+ * to handle the port's hardware device, or should add the port to an
+ * existing modem and return that modem object. If an error is encountered
+ * while claiming the port, the error information should be returned in the
+ * error argument, and the plugin should return NULL.
+ */
+ MMModem * (*grab_port) (MMPlugin *self,
+ const char *subsys,
+ const char *name,
+ GError **error);
+};
+
+GType mm_plugin_get_type (void);
+
+const char *mm_plugin_get_name (MMPlugin *plugin);
+
+MMPluginSupportsResult mm_plugin_supports_port (MMPlugin *plugin,
+ const char *subsys,
+ const char *name,
+ MMSupportsPortResultFunc callback,
+ gpointer user_data);
+
+void mm_plugin_cancel_supports_port (MMPlugin *plugin,
+ const char *subsys,
+ const char *name);
+
+MMModem *mm_plugin_grab_port (MMPlugin *plugin,
+ const char *subsys,
+ const char *name,
+ GError **error);
+
+#endif /* MM_PLUGIN_H */
+
diff --git a/src/mm-port.c b/src/mm-port.c
new file mode 100644
index 0000000..7e8edc4
--- /dev/null
+++ b/src/mm-port.c
@@ -0,0 +1,278 @@
+/* -*- 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) 2009 Red Hat, Inc.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "mm-port.h"
+
+G_DEFINE_TYPE (MMPort, mm_port, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_DEVICE,
+ PROP_SUBSYS,
+ PROP_TYPE,
+ PROP_CARRIER_DETECT,
+ PROP_CONNECTED,
+
+ LAST_PROP
+};
+
+#define MM_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_PORT, MMPortPrivate))
+
+typedef struct {
+ char *device;
+ MMPortSubsys subsys;
+ MMPortType ptype;
+ gboolean carrier_detect;
+ gboolean connected;
+} MMPortPrivate;
+
+/*****************************************************************************/
+
+static GObject*
+constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+ MMPortPrivate *priv;
+
+ object = G_OBJECT_CLASS (mm_port_parent_class)->constructor (type,
+ n_construct_params,
+ construct_params);
+ if (!object)
+ return NULL;
+
+ priv = MM_PORT_GET_PRIVATE (object);
+
+ if (!priv->device) {
+ g_warning ("MMPort: no device provided");
+ g_object_unref (object);
+ return NULL;
+ }
+
+ if (priv->subsys == MM_PORT_SUBSYS_UNKNOWN) {
+ g_warning ("MMPort: invalid port subsystem");
+ g_object_unref (object);
+ return NULL;
+ }
+
+ if (priv->ptype == MM_PORT_TYPE_UNKNOWN) {
+ g_warning ("MMPort: invalid port type");
+ g_object_unref (object);
+ return NULL;
+ }
+
+ return object;
+}
+
+
+const char *
+mm_port_get_device (MMPort *self)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (MM_IS_PORT (self), NULL);
+
+ return MM_PORT_GET_PRIVATE (self)->device;
+}
+
+MMPortSubsys
+mm_port_get_subsys (MMPort *self)
+{
+ g_return_val_if_fail (self != NULL, MM_PORT_SUBSYS_UNKNOWN);
+ g_return_val_if_fail (MM_IS_PORT (self), MM_PORT_SUBSYS_UNKNOWN);
+
+ return MM_PORT_GET_PRIVATE (self)->subsys;
+}
+
+MMPortType
+mm_port_get_port_type (MMPort *self)
+{
+ g_return_val_if_fail (self != NULL, MM_PORT_TYPE_UNKNOWN);
+ g_return_val_if_fail (MM_IS_PORT (self), MM_PORT_TYPE_UNKNOWN);
+
+ return MM_PORT_GET_PRIVATE (self)->ptype;
+}
+
+gboolean
+mm_port_get_carrier_detect (MMPort *self)
+{
+ g_return_val_if_fail (self != NULL, MM_PORT_TYPE_UNKNOWN);
+ g_return_val_if_fail (MM_IS_PORT (self), MM_PORT_TYPE_UNKNOWN);
+
+ return MM_PORT_GET_PRIVATE (self)->carrier_detect;
+}
+
+gboolean
+mm_port_get_connected (MMPort *self)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (MM_IS_PORT (self), FALSE);
+
+ return MM_PORT_GET_PRIVATE (self)->connected;
+}
+
+void
+mm_port_set_connected (MMPort *self, gboolean connected)
+{
+ MMPortPrivate *priv;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (MM_IS_PORT (self));
+
+ priv = MM_PORT_GET_PRIVATE (self);
+ if (priv->connected != connected) {
+ priv->connected = connected;
+ g_object_notify (G_OBJECT (self), MM_PORT_CONNECTED);
+ }
+}
+
+/*****************************************************************************/
+
+static void
+mm_port_init (MMPort *self)
+{
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ MMPortPrivate *priv = MM_PORT_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case PROP_DEVICE:
+ /* Construct only */
+ priv->device = g_value_dup_string (value);
+ break;
+ case PROP_SUBSYS:
+ /* Construct only */
+ priv->subsys = g_value_get_uint (value);
+ break;
+ case PROP_TYPE:
+ /* Construct only */
+ priv->ptype = g_value_get_uint (value);
+ break;
+ case PROP_CARRIER_DETECT:
+ priv->carrier_detect = g_value_get_boolean (value);
+ break;
+ case PROP_CONNECTED:
+ priv->connected = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ MMPortPrivate *priv = MM_PORT_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case PROP_DEVICE:
+ g_value_set_string (value, priv->device);
+ break;
+ case PROP_SUBSYS:
+ g_value_set_uint (value, priv->subsys);
+ break;
+ case PROP_TYPE:
+ g_value_set_uint (value, priv->ptype);
+ break;
+ case PROP_CARRIER_DETECT:
+ g_value_set_boolean (value, priv->carrier_detect);
+ break;
+ case PROP_CONNECTED:
+ g_value_set_boolean (value, priv->connected);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ MMPortPrivate *priv = MM_PORT_GET_PRIVATE (object);
+
+ g_free (priv->device);
+
+ G_OBJECT_CLASS (mm_port_parent_class)->finalize (object);
+}
+
+static void
+mm_port_class_init (MMPortClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMPortPrivate));
+
+ /* Virtual methods */
+ object_class->constructor = constructor;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->finalize = finalize;
+
+ g_object_class_install_property
+ (object_class, PROP_DEVICE,
+ g_param_spec_string (MM_PORT_DEVICE,
+ "Device",
+ "Device",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property
+ (object_class, PROP_SUBSYS,
+ g_param_spec_uint (MM_PORT_SUBSYS,
+ "Subsystem",
+ "Subsystem",
+ MM_PORT_SUBSYS_UNKNOWN,
+ MM_PORT_SUBSYS_LAST,
+ MM_PORT_SUBSYS_UNKNOWN,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property
+ (object_class, PROP_TYPE,
+ g_param_spec_uint (MM_PORT_TYPE,
+ "Type",
+ "Type",
+ MM_PORT_TYPE_UNKNOWN,
+ MM_PORT_TYPE_LAST,
+ MM_PORT_TYPE_UNKNOWN,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property
+ (object_class, PROP_CARRIER_DETECT,
+ g_param_spec_boolean (MM_PORT_CARRIER_DETECT,
+ "Carrier Detect",
+ "Has Carrier Detect",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property
+ (object_class, PROP_CONNECTED,
+ g_param_spec_boolean (MM_PORT_CONNECTED,
+ "Connected",
+ "Is connected for data and not usable for control",
+ FALSE,
+ G_PARAM_READWRITE));
+}
diff --git a/src/mm-port.h b/src/mm-port.h
new file mode 100644
index 0000000..b537618
--- /dev/null
+++ b/src/mm-port.h
@@ -0,0 +1,79 @@
+/* -*- 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) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_PORT_H
+#define MM_PORT_H
+
+#include <glib.h>
+#include <glib/gtypes.h>
+#include <glib-object.h>
+
+typedef enum {
+ MM_PORT_SUBSYS_UNKNOWN = 0x0,
+ MM_PORT_SUBSYS_TTY,
+ MM_PORT_SUBSYS_NET,
+
+ MM_PORT_SUBSYS_LAST = MM_PORT_SUBSYS_NET
+} MMPortSubsys;
+
+typedef enum {
+ MM_PORT_TYPE_UNKNOWN = 0x0,
+ MM_PORT_TYPE_PRIMARY,
+ MM_PORT_TYPE_SECONDARY,
+ MM_PORT_TYPE_IGNORED,
+
+ MM_PORT_TYPE_LAST = MM_PORT_TYPE_IGNORED
+} MMPortType;
+
+#define MM_TYPE_PORT (mm_port_get_type ())
+#define MM_PORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PORT, MMPort))
+#define MM_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PORT, MMPortClass))
+#define MM_IS_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PORT))
+#define MM_IS_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PORT))
+#define MM_PORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PORT, MMPortClass))
+
+#define MM_PORT_DEVICE "device"
+#define MM_PORT_SUBSYS "subsys"
+#define MM_PORT_TYPE "type"
+#define MM_PORT_CARRIER_DETECT "carrier-detect"
+#define MM_PORT_CONNECTED "connected"
+
+typedef struct _MMPort MMPort;
+typedef struct _MMPortClass MMPortClass;
+
+struct _MMPort {
+ GObject parent;
+};
+
+struct _MMPortClass {
+ GObjectClass parent;
+};
+
+GType mm_port_get_type (void);
+
+const char * mm_port_get_device (MMPort *self);
+
+MMPortSubsys mm_port_get_subsys (MMPort *self);
+
+MMPortType mm_port_get_port_type (MMPort *self);
+
+gboolean mm_port_get_carrier_detect (MMPort *self);
+
+gboolean mm_port_get_connected (MMPort *self);
+
+void mm_port_set_connected (MMPort *self, gboolean connected);
+
+#endif /* MM_PORT_H */
+
diff --git a/src/mm-properties-changed-signal.c b/src/mm-properties-changed-signal.c
new file mode 100644
index 0000000..b7f3672
--- /dev/null
+++ b/src/mm-properties-changed-signal.c
@@ -0,0 +1,276 @@
+/* -*- 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) 2007 - 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2009 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <dbus/dbus-glib.h>
+#include "mm-marshal.h"
+#include "mm-properties-changed-signal.h"
+
+#define DBUS_TYPE_G_MAP_OF_VARIANT (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE))
+
+#define PC_SIGNAL_NAME "mm-properties-changed"
+#define MM_DBUS_PROPERTY_CHANGED "MM_DBUS_PROPERTY_CHANGED"
+
+typedef struct {
+ /* Whitelist of GObject property names for which changes will be emitted
+ * over the bus.
+ *
+ * Mapping of {property-name -> dbus-interface}
+ */
+ GHashTable *registered;
+
+ /* Table of each D-Bus interface of the object for which one or more
+ * properties have changed, and those properties and their new values.
+ * Destroyed after the changed signal has been sent.
+ *
+ * Mapping of {dbus-interface -> {property-name -> value}}
+ */
+ GHashTable *hash;
+
+ gulong signal_id;
+ guint idle_id;
+} PropertiesChangedInfo;
+
+static void
+destroy_value (gpointer data)
+{
+ GValue *val = (GValue *) data;
+
+ g_value_unset (val);
+ g_slice_free (GValue, val);
+}
+
+static PropertiesChangedInfo *
+properties_changed_info_new (void)
+{
+ PropertiesChangedInfo *info;
+
+ info = g_slice_new0 (PropertiesChangedInfo);
+
+ info->registered = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ info->hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_hash_table_destroy);
+ return info;
+}
+
+static void
+properties_changed_info_destroy (gpointer data)
+{
+ PropertiesChangedInfo *info = (PropertiesChangedInfo *) data;
+
+ if (info->idle_id)
+ g_source_remove (info->idle_id);
+
+ g_hash_table_destroy (info->hash);
+ g_hash_table_destroy (info->registered);
+ g_slice_free (PropertiesChangedInfo, info);
+}
+
+#ifdef DEBUG
+static void
+add_to_string (gpointer key, gpointer value, gpointer user_data)
+{
+ char *buf = (char *) user_data;
+ GValue str_val = { 0, };
+
+ g_value_init (&str_val, G_TYPE_STRING);
+ if (!g_value_transform ((GValue *) value, &str_val)) {
+ if (G_VALUE_HOLDS_OBJECT (value)) {
+ GObject *obj = g_value_get_object (value);
+
+ if (g_value_get_object (value)) {
+ sprintf (buf + strlen (buf), "{%s: %p (%s)}, ",
+ (const char *) key, obj, G_OBJECT_TYPE_NAME (obj));
+ } else {
+ sprintf (buf + strlen (buf), "{%s: %p}, ", (const char *) key, obj);
+ }
+ } else
+ sprintf (buf + strlen (buf), "{%s: <transform error>}, ", (const char *) key);
+ } else {
+ sprintf (buf + strlen (buf), "{%s: %s}, ", (const char *) key, g_value_get_string (&str_val));
+ }
+ g_value_unset (&str_val);
+}
+#endif
+
+static gboolean
+properties_changed (gpointer data)
+{
+ GObject *object = G_OBJECT (data);
+ PropertiesChangedInfo *info;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ info = (PropertiesChangedInfo *) g_object_get_data (object, MM_DBUS_PROPERTY_CHANGED);
+ g_assert (info);
+
+ g_hash_table_iter_init (&iter, info->hash);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ const char *interface = (const char *) key;
+ GHashTable *props = (GHashTable *) value;
+
+#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);
+ }
+#endif
+
+ /* Send the PropertiesChanged signal */
+ g_signal_emit (object, info->signal_id, 0, interface, props);
+ }
+ g_hash_table_remove_all (info->hash);
+
+ return FALSE;
+}
+
+static void
+idle_id_reset (gpointer data)
+{
+ GObject *object = G_OBJECT (data);
+ PropertiesChangedInfo *info = (PropertiesChangedInfo *) g_object_get_data (object, MM_DBUS_PROPERTY_CHANGED);
+
+ /* info is unset when the object is being destroyed */
+ if (info)
+ info->idle_id = 0;
+}
+
+static char*
+uscore_to_wincaps (const char *uscore)
+{
+ const char *p;
+ GString *str;
+ gboolean last_was_uscore;
+
+ last_was_uscore = TRUE;
+
+ str = g_string_new (NULL);
+ p = uscore;
+ while (p && *p) {
+ if (*p == '-' || *p == '_')
+ last_was_uscore = TRUE;
+ else {
+ if (last_was_uscore) {
+ g_string_append_c (str, g_ascii_toupper (*p));
+ last_was_uscore = FALSE;
+ } else
+ g_string_append_c (str, *p);
+ }
+ ++p;
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+static PropertiesChangedInfo *
+get_properties_changed_info (GObject *object)
+{
+ PropertiesChangedInfo *info = NULL;
+
+ info = (PropertiesChangedInfo *) g_object_get_data (object, MM_DBUS_PROPERTY_CHANGED);
+ 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);
+ return info;
+}
+
+static void
+notify (GObject *object, GParamSpec *pspec)
+{
+ GHashTable *interfaces;
+ PropertiesChangedInfo *info;
+ const char *interface;
+ GValue *value;
+
+ info = get_properties_changed_info (object);
+
+ interface = g_hash_table_lookup (info->registered, pspec->name);
+ if (!interface)
+ 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);
+ 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);
+ }
+
+ /* Now put the changed property value into the hash table of changed values
+ * for its D-Bus interface.
+ */
+ 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);
+
+ if (!info->idle_id)
+ info->idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, properties_changed, object, idle_id_reset);
+}
+
+void
+mm_properties_changed_signal_register_property (GObject *object,
+ const char *property,
+ const char *interface)
+{
+ PropertiesChangedInfo *info;
+ const char *tmp;
+
+ /* All exported properties need to be registered explicitly for now since
+ * dbus-glib doesn't expose any method to find out the properties registered
+ * in the XML.
+ */
+
+ info = get_properties_changed_info (object);
+ tmp = g_hash_table_lookup (info->registered, property);
+ if (tmp) {
+ 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));
+}
+
+guint
+mm_properties_changed_signal_new (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);
+
+ return id;
+}
+
diff --git a/src/mm-properties-changed-signal.h b/src/mm-properties-changed-signal.h
new file mode 100644
index 0000000..60e71b9
--- /dev/null
+++ b/src/mm-properties-changed-signal.h
@@ -0,0 +1,28 @@
+/* -*- 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) 2007 - 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2009 Red Hat, Inc.
+ */
+
+#ifndef _MM_PROPERTIES_CHANGED_SIGNAL_H_
+#define _MM_PROPERTIES_CHANGED_SIGNAL_H_
+
+#include <glib-object.h>
+
+guint mm_properties_changed_signal_new (GObjectClass *object_class);
+
+void mm_properties_changed_signal_register_property (GObject *object,
+ const char *property,
+ const char *interface);
+
+#endif /* _MM_PROPERTIES_CHANGED_SIGNAL_H_ */
diff --git a/src/mm-serial-parsers.c b/src/mm-serial-parsers.c
new file mode 100644
index 0000000..58985d9
--- /dev/null
+++ b/src/mm-serial-parsers.c
@@ -0,0 +1,372 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "mm-serial-parsers.h"
+#include "mm-errors.h"
+
+/* Clean up the response by removing control characters like <CR><LF> etc */
+static void
+response_clean (GString *response)
+{
+ char *s;
+
+ /* Ends with one or more '<CR><LF>' */
+ s = response->str + response->len - 1;
+ while ((s > response->str) && (*s == '\n') && (*(s - 1) == '\r')) {
+ g_string_truncate (response, response->len - 2);
+ s -= 2;
+ }
+
+ /* Starts with one or more '<CR><LF>' */
+ s = response->str;
+ while ((response->len >= 2) && (*s == '\r') && (*(s + 1) == '\n')) {
+ g_string_erase (response, 0, 2);
+ s = response->str;
+ }
+}
+
+
+static gboolean
+remove_eval_cb (const GMatchInfo *match_info,
+ GString *result,
+ gpointer user_data)
+{
+ int *result_len = (int *) user_data;
+ int start;
+ int end;
+
+ if (g_match_info_fetch_pos (match_info, 0, &start, &end))
+ *result_len -= (end - start);
+
+ return TRUE;
+}
+
+static void
+remove_matches (GRegex *r, GString *string)
+{
+ char *str;
+ int result_len = string->len;
+
+ str = g_regex_replace_eval (r, string->str, string->len, 0, 0,
+ remove_eval_cb, &result_len, NULL);
+
+ g_string_truncate (string, 0);
+ g_string_append_len (string, str, result_len);
+ g_free (str);
+}
+
+typedef struct {
+ GRegex *generic_response;
+ GRegex *detailed_error;
+} MMSerialParserV0;
+
+gpointer
+mm_serial_parser_v0_new (void)
+{
+ MMSerialParserV0 *parser;
+ GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW | G_REGEX_OPTIMIZE;
+
+ parser = g_slice_new (MMSerialParserV0);
+
+ parser->generic_response = g_regex_new ("(\\d)\\0?\\r$", flags, 0, NULL);
+ parser->detailed_error = g_regex_new ("\\+CME ERROR: (\\d+)\\r\\n$", flags, 0, NULL);
+
+ return parser;
+}
+
+gboolean
+mm_serial_parser_v0_parse (gpointer data,
+ GString *response,
+ GError **error)
+{
+ MMSerialParserV0 *parser = (MMSerialParserV0 *) data;
+ GMatchInfo *match_info;
+ char *str;
+ GError *local_error = NULL;
+ int code;
+ gboolean found;
+
+ g_return_val_if_fail (parser != NULL, FALSE);
+ g_return_val_if_fail (response != NULL, FALSE);
+
+ if (G_UNLIKELY (!response->len || !strlen (response->str)))
+ return FALSE;
+
+ found = g_regex_match_full (parser->generic_response, response->str, response->len, 0, 0, &match_info, NULL);
+ if (found) {
+ str = g_match_info_fetch (match_info, 1);
+ if (str) {
+ code = atoi (str);
+ g_free (str);
+ } else
+ code = MM_MOBILE_ERROR_UNKNOWN;
+
+ g_match_info_free (match_info);
+
+ switch (code) {
+ case 0: /* OK */
+ break;
+ case 1: /* CONNECT */
+ break;
+ case 3: /* NO CARRIER */
+ local_error = mm_modem_connect_error_for_code (MM_MODEM_CONNECT_ERROR_NO_CARRIER);
+ break;
+ case 4: /* ERROR */
+ local_error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN);
+ break;
+ case 6: /* NO DIALTONE */
+ local_error = mm_modem_connect_error_for_code (MM_MODEM_CONNECT_ERROR_NO_DIALTONE);
+ break;
+ case 7: /* BUSY */
+ local_error = mm_modem_connect_error_for_code (MM_MODEM_CONNECT_ERROR_BUSY);
+ break;
+ case 8: /* NO ANSWER */
+ local_error = mm_modem_connect_error_for_code (MM_MODEM_CONNECT_ERROR_NO_ANSWER);
+ break;
+ default:
+ local_error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN);
+ break;
+ }
+
+ remove_matches (parser->generic_response, response);
+ }
+
+ if (!found) {
+ found = g_regex_match_full (parser->detailed_error, response->str, response->len, 0, 0, &match_info, NULL);
+
+ if (found) {
+ str = g_match_info_fetch (match_info, 1);
+ if (str) {
+ code = atoi (str);
+ g_free (str);
+ } else
+ code = MM_MOBILE_ERROR_UNKNOWN;
+
+ g_match_info_free (match_info);
+ local_error = mm_mobile_error_for_code (code);
+ }
+ }
+
+ if (found)
+ response_clean (response);
+
+ if (local_error) {
+ g_debug ("Got failure code %d: %s", local_error->code, local_error->message);
+ g_propagate_error (error, local_error);
+ }
+
+ return found;
+}
+
+void
+mm_serial_parser_v0_destroy (gpointer data)
+{
+ MMSerialParserV0 *parser = (MMSerialParserV0 *) data;
+
+ g_return_if_fail (parser != NULL);
+
+ g_regex_unref (parser->generic_response);
+ g_regex_unref (parser->detailed_error);
+
+ g_slice_free (MMSerialParserV0, data);
+}
+
+typedef struct {
+ GRegex *regex_ok;
+ GRegex *regex_connect;
+ GRegex *regex_detailed_error;
+ GRegex *regex_unknown_error;
+ GRegex *regex_connect_failed;
+} MMSerialParserV1;
+
+gpointer
+mm_serial_parser_v1_new (void)
+{
+ MMSerialParserV1 *parser;
+ GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW | G_REGEX_OPTIMIZE;
+
+ parser = g_slice_new (MMSerialParserV1);
+
+ parser->regex_ok = g_regex_new ("\\r\\nOK(\\r\\n)+$", flags, 0, NULL);
+ parser->regex_connect = g_regex_new ("\\r\\nCONNECT.*\\r\\n", flags, 0, NULL);
+ parser->regex_detailed_error = g_regex_new ("\\r\\n\\+CME ERROR: (\\d+)\\r\\n$", flags, 0, NULL);
+ parser->regex_unknown_error = g_regex_new ("\\r\\n(ERROR)|(COMMAND NOT SUPPORT)\\r\\n$", flags, 0, NULL);
+ parser->regex_connect_failed = g_regex_new ("\\r\\n(NO CARRIER)|(BUSY)|(NO ANSWER)|(NO DIALTONE)\\r\\n$", flags, 0, NULL);
+
+ return parser;
+}
+
+gboolean
+mm_serial_parser_v1_parse (gpointer data,
+ GString *response,
+ GError **error)
+{
+ MMSerialParserV1 *parser = (MMSerialParserV1 *) data;
+ GMatchInfo *match_info;
+ GError *local_error;
+ int code;
+ gboolean found = FALSE;
+
+ g_return_val_if_fail (parser != NULL, FALSE);
+ g_return_val_if_fail (response != NULL, FALSE);
+
+ if (G_UNLIKELY (!response->len || !strlen (response->str)))
+ return FALSE;
+
+ /* First, check for successful responses */
+
+ found = g_regex_match_full (parser->regex_ok, response->str, response->len, 0, 0, NULL, NULL);
+ if (found)
+ remove_matches (parser->regex_ok, response);
+ else
+ found = g_regex_match_full (parser->regex_connect, response->str, response->len, 0, 0, NULL, NULL);
+
+ if (found) {
+ response_clean (response);
+ return TRUE;
+ }
+
+ /* Now failures */
+ code = MM_MOBILE_ERROR_UNKNOWN;
+ local_error = NULL;
+
+ found = g_regex_match_full (parser->regex_detailed_error,
+ response->str, response->len,
+ 0, 0, &match_info, NULL);
+
+ if (found) {
+ char *str;
+
+ str = g_match_info_fetch (match_info, 1);
+ if (str) {
+ code = atoi (str);
+ g_free (str);
+ }
+ g_match_info_free (match_info);
+ } else
+ found = g_regex_match_full (parser->regex_unknown_error, response->str, response->len, 0, 0, NULL, NULL);
+
+ if (found)
+ local_error = mm_mobile_error_for_code (code);
+ else {
+ found = g_regex_match_full (parser->regex_connect_failed,
+ response->str, response->len,
+ 0, 0, &match_info, NULL);
+ if (found) {
+ char *str;
+
+ str = g_match_info_fetch (match_info, 1);
+ if (str) {
+ if (!strcmp (str, "NO CARRIER"))
+ code = MM_MODEM_CONNECT_ERROR_NO_CARRIER;
+ else if (!strcmp (str, "BUSY"))
+ code = MM_MODEM_CONNECT_ERROR_BUSY;
+ else if (!strcmp (str, "NO ANSWER"))
+ code = MM_MODEM_CONNECT_ERROR_NO_ANSWER;
+ else if (!strcmp (str, "NO DIALTONE"))
+ code = MM_MODEM_CONNECT_ERROR_NO_DIALTONE;
+ else
+ /* uhm... make something up (yes, ok, lie!). */
+ code = MM_MODEM_CONNECT_ERROR_NO_CARRIER;
+
+ g_free (str);
+ }
+ g_match_info_free (match_info);
+
+ local_error = mm_modem_connect_error_for_code (code);
+ }
+ }
+
+ if (found)
+ response_clean (response);
+
+ if (local_error) {
+ g_debug ("Got failure code %d: %s", local_error->code, local_error->message);
+ g_propagate_error (error, local_error);
+ }
+
+ return found;
+}
+
+void
+mm_serial_parser_v1_destroy (gpointer data)
+{
+ MMSerialParserV1 *parser = (MMSerialParserV1 *) data;
+
+ g_return_if_fail (parser != NULL);
+
+ g_regex_unref (parser->regex_ok);
+ g_regex_unref (parser->regex_connect);
+ g_regex_unref (parser->regex_detailed_error);
+ g_regex_unref (parser->regex_unknown_error);
+ g_regex_unref (parser->regex_connect_failed);
+
+ g_slice_free (MMSerialParserV1, data);
+}
+
+typedef struct {
+ gpointer v1;
+ GRegex *regex_echo;
+} MMSerialParserV1E1;
+
+gpointer
+mm_serial_parser_v1_e1_new (void)
+{
+ MMSerialParserV1E1 *parser;
+ GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW | G_REGEX_OPTIMIZE;
+
+ parser = g_slice_new (MMSerialParserV1E1);
+ parser->v1 = mm_serial_parser_v1_new ();
+
+ /* Does not start with '<CR><LF>' and ends with '<CR>'. */
+ parser->regex_echo = g_regex_new ("^(?!\\r\\n).+\\r", flags, 0, NULL);
+
+ return parser;
+}
+
+gboolean
+mm_serial_parser_v1_e1_parse (gpointer data,
+ GString *response,
+ GError **error)
+{
+ MMSerialParserV1E1 *parser = (MMSerialParserV1E1 *) data;
+ GMatchInfo *match_info = NULL;
+
+ /* Remove the command echo */
+ if (g_regex_match_full (parser->regex_echo, response->str, response->len, 0, 0, &match_info, NULL)) {
+ gchar *match = g_match_info_fetch (match_info, 0);
+
+ g_string_erase (response, 0, strlen (match));
+ g_free (match);
+ g_match_info_free (match_info);
+ }
+
+ return mm_serial_parser_v1_parse (parser->v1, response, error);
+}
+
+void
+mm_serial_parser_v1_e1_destroy (gpointer data)
+{
+ MMSerialParserV1E1 *parser = (MMSerialParserV1E1 *) data;
+
+ g_regex_unref (parser->regex_echo);
+ mm_serial_parser_v1_destroy (parser->v1);
+
+ g_slice_free (MMSerialParserV1E1, data);
+}
diff --git a/src/mm-serial-parsers.h b/src/mm-serial-parsers.h
new file mode 100644
index 0000000..3e1fb9f
--- /dev/null
+++ b/src/mm-serial-parsers.h
@@ -0,0 +1,44 @@
+/* -*- 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 - 2009 Novell, Inc.
+ */
+
+#ifndef MM_SERIAL_PARSERS_H
+#define MM_SERIAL_PARSERS_H
+
+#include <glib.h>
+
+gpointer mm_serial_parser_v0_new (void);
+gboolean mm_serial_parser_v0_parse (gpointer parser,
+ GString *response,
+ GError **error);
+
+void mm_serial_parser_v0_destroy (gpointer parser);
+
+
+gpointer mm_serial_parser_v1_new (void);
+gboolean mm_serial_parser_v1_parse (gpointer parser,
+ GString *response,
+ GError **error);
+
+void mm_serial_parser_v1_destroy (gpointer parser);
+
+
+gpointer mm_serial_parser_v1_e1_new (void);
+gboolean mm_serial_parser_v1_e1_parse (gpointer parser,
+ GString *response,
+ GError **error);
+
+void mm_serial_parser_v1_e1_destroy (gpointer parser);
+
+#endif /* MM_SERIAL_PARSERS_H */
diff --git a/src/mm-serial-port.c b/src/mm-serial-port.c
new file mode 100644
index 0000000..2600ae5
--- /dev/null
+++ b/src/mm-serial-port.c
@@ -0,0 +1,1281 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#define _GNU_SOURCE /* for strcasestr() */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <termio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <string.h>
+
+#include "mm-serial-port.h"
+#include "mm-errors.h"
+#include "mm-options.h"
+
+static gboolean mm_serial_port_queue_process (gpointer data);
+
+G_DEFINE_TYPE (MMSerialPort, mm_serial_port, MM_TYPE_PORT)
+
+enum {
+ PROP_0,
+ PROP_BAUD,
+ PROP_BITS,
+ PROP_PARITY,
+ PROP_STOPBITS,
+ PROP_SEND_DELAY,
+
+ LAST_PROP
+};
+
+#define SERIAL_BUF_SIZE 2048
+
+#define MM_SERIAL_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_SERIAL_PORT, MMSerialPortPrivate))
+
+typedef struct {
+ int fd;
+ GHashTable *reply_cache;
+ GIOChannel *channel;
+ GQueue *queue;
+ GString *command;
+ GString *response;
+
+ gboolean connected;
+
+ /* Response parser data */
+ MMSerialResponseParserFn response_parser_fn;
+ gpointer response_parser_user_data;
+ GDestroyNotify response_parser_notify;
+ GSList *unsolicited_msg_handlers;
+
+ struct termios old_t;
+
+ guint baud;
+ guint bits;
+ char parity;
+ guint stopbits;
+ guint64 send_delay;
+
+ guint queue_schedule;
+ guint watch_id;
+ guint timeout_id;
+
+ guint flash_id;
+ guint connected_id;
+} MMSerialPortPrivate;
+
+#if 0
+static const char *
+baud_to_string (int baud)
+{
+ const char *speed = NULL;
+
+ switch (baud) {
+ case B0:
+ speed = "0";
+ break;
+ case B50:
+ speed = "50";
+ break;
+ case B75:
+ speed = "75";
+ break;
+ case B110:
+ speed = "110";
+ break;
+ case B150:
+ speed = "150";
+ break;
+ case B300:
+ speed = "300";
+ break;
+ case B600:
+ speed = "600";
+ break;
+ case B1200:
+ speed = "1200";
+ break;
+ case B2400:
+ speed = "2400";
+ break;
+ case B4800:
+ speed = "4800";
+ break;
+ case B9600:
+ speed = "9600";
+ break;
+ case B19200:
+ speed = "19200";
+ break;
+ case B38400:
+ speed = "38400";
+ break;
+ case B57600:
+ speed = "57600";
+ break;
+ case B115200:
+ speed = "115200";
+ break;
+ case B460800:
+ speed = "460800";
+ break;
+ default:
+ break;
+ }
+
+ return speed;
+}
+
+void
+mm_serial_port_print_config (MMSerialPort *port, const char *detail)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (port);
+ struct termio stbuf;
+ int err;
+
+ err = ioctl (priv->fd, TCGETA, &stbuf);
+ if (err) {
+ g_warning ("*** %s (%s): (%s) TCGETA error %d",
+ __func__, detail, mm_port_get_device (MM_PORT (port)), errno);
+ 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));
+}
+#endif
+
+typedef struct {
+ GRegex *regex;
+ MMSerialUnsolicitedMsgFn callback;
+ gpointer user_data;
+ GDestroyNotify notify;
+} MMUnsolicitedMsgHandler;
+
+static void
+mm_serial_port_set_cached_reply (MMSerialPort *self,
+ const char *command,
+ const char *reply)
+{
+ if (reply)
+ g_hash_table_insert (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache,
+ g_strdup (command),
+ g_strdup (reply));
+ else
+ g_hash_table_remove (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command);
+}
+
+static const char *
+mm_serial_port_get_cached_reply (MMSerialPort *self,
+ const char *command)
+{
+ return (char *) g_hash_table_lookup (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command);
+}
+
+static int
+parse_baudrate (guint i)
+{
+ int speed;
+
+ switch (i) {
+ case 0:
+ speed = B0;
+ break;
+ case 50:
+ speed = B50;
+ break;
+ case 75:
+ speed = B75;
+ break;
+ case 110:
+ speed = B110;
+ break;
+ case 150:
+ speed = B150;
+ break;
+ case 300:
+ speed = B300;
+ break;
+ case 600:
+ speed = B600;
+ break;
+ case 1200:
+ speed = B1200;
+ break;
+ case 2400:
+ speed = B2400;
+ break;
+ case 4800:
+ speed = B4800;
+ break;
+ case 9600:
+ speed = B9600;
+ break;
+ case 19200:
+ speed = B19200;
+ break;
+ case 38400:
+ speed = B38400;
+ break;
+ case 57600:
+ speed = B57600;
+ break;
+ case 115200:
+ speed = B115200;
+ break;
+ case 460800:
+ speed = B460800;
+ break;
+ default:
+ g_warning ("Invalid baudrate '%d'", i);
+ speed = B9600;
+ }
+
+ return speed;
+}
+
+static int
+parse_bits (guint i)
+{
+ int bits;
+
+ switch (i) {
+ case 5:
+ bits = CS5;
+ break;
+ case 6:
+ bits = CS6;
+ break;
+ case 7:
+ bits = CS7;
+ break;
+ case 8:
+ bits = CS8;
+ break;
+ default:
+ g_warning ("Invalid bits (%d). Valid values are 5, 6, 7, 8.", i);
+ bits = CS8;
+ }
+
+ return bits;
+}
+
+static int
+parse_parity (char c)
+{
+ int parity;
+
+ switch (c) {
+ case 'n':
+ case 'N':
+ parity = 0;
+ break;
+ case 'e':
+ case 'E':
+ parity = PARENB;
+ break;
+ case 'o':
+ case 'O':
+ parity = PARENB | PARODD;
+ break;
+ default:
+ g_warning ("Invalid parity (%c). Valid values are n, e, o", c);
+ parity = 0;
+ }
+
+ return parity;
+}
+
+static int
+parse_stopbits (guint i)
+{
+ int stopbits;
+
+ switch (i) {
+ case 1:
+ stopbits = 0;
+ break;
+ case 2:
+ stopbits = CSTOPB;
+ break;
+ default:
+ g_warning ("Invalid stop bits (%d). Valid values are 1 and 2)", i);
+ stopbits = 0;
+ }
+
+ return stopbits;
+}
+
+static gboolean
+config_fd (MMSerialPort *self, GError **error)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ struct termio stbuf;
+ int speed;
+ int bits;
+ int parity;
+ int stopbits;
+
+ speed = parse_baudrate (priv->baud);
+ bits = parse_bits (priv->bits);
+ parity = parse_parity (priv->parity);
+ stopbits = parse_stopbits (priv->stopbits);
+
+ memset (&stbuf, 0, sizeof (struct termio));
+ if (ioctl (priv->fd, TCGETA, &stbuf) != 0) {
+ g_warning ("%s (%s): TCGETA error: %d",
+ __func__,
+ mm_port_get_device (MM_PORT (self)),
+ errno);
+ }
+
+ stbuf.c_iflag &= ~(IGNCR | ICRNL | IUCLC | INPCK | IXON | IXANY | IGNPAR );
+ stbuf.c_oflag &= ~(OPOST | OLCUC | OCRNL | ONLCR | ONLRET);
+ stbuf.c_lflag &= ~(ICANON | XCASE | ECHO | ECHOE | ECHONL);
+ stbuf.c_lflag &= ~(ECHO | ECHOE);
+ stbuf.c_cc[VMIN] = 1;
+ stbuf.c_cc[VTIME] = 0;
+ stbuf.c_cc[VEOF] = 1;
+
+ stbuf.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | CLOCAL | PARENB);
+ stbuf.c_cflag |= (speed | bits | CREAD | 0 | parity | stopbits);
+
+ if (ioctl (priv->fd, TCSETA, &stbuf) < 0) {
+ g_set_error (error,
+ MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "%s: failed to set serial port attributes; errno %d",
+ __func__, errno);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+serial_debug (MMSerialPort *self, const char *prefix, const char *buf, int len)
+{
+ static GString *debug = NULL;
+ const char *s;
+
+ if (!mm_options_debug ())
+ return;
+
+ if (len < 0)
+ len = strlen (buf);
+
+ if (!debug)
+ debug = g_string_sized_new (256);
+
+ g_string_append (debug, prefix);
+ g_string_append (debug, " '");
+
+ s = buf;
+ while (len--) {
+ if (g_ascii_isprint (*s))
+ g_string_append_c (debug, *s);
+ else if (*s == '\r')
+ g_string_append (debug, "<CR>");
+ else if (*s == '\n')
+ g_string_append (debug, "<LF>");
+ else
+ g_string_append_printf (debug, "\\%d", *s);
+
+ s++;
+ }
+
+ g_string_append_c (debug, '\'');
+ g_debug ("(%s): %s", mm_port_get_device (MM_PORT (self)), debug->str);
+ g_string_truncate (debug, 0);
+}
+
+static gboolean
+mm_serial_port_send_command (MMSerialPort *self,
+ const char *command,
+ GError **error)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ const char *s;
+ int status;
+ int eagain_count = 1000;
+
+ if (priv->fd < 0) {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED,
+ "%s", "Sending command failed: device is not enabled");
+ return FALSE;
+ }
+
+ if (mm_port_get_connected (MM_PORT (self))) {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED,
+ "%s", "Sending command failed: device is connected");
+ return FALSE;
+ }
+
+ g_string_truncate (priv->command, g_str_has_prefix (command, "AT") ? 0 : 2);
+ g_string_append (priv->command, command);
+
+ if (command[strlen (command)] != '\r')
+ g_string_append_c (priv->command, '\r');
+
+ serial_debug (self, "-->", priv->command->str, -1);
+
+ /* Only accept about 3 seconds of EAGAIN */
+ if (priv->send_delay > 0)
+ eagain_count = 3000000 / priv->send_delay;
+
+ s = priv->command->str;
+ while (*s) {
+ status = write (priv->fd, s, 1);
+ if (status < 0) {
+ if (errno == EAGAIN) {
+ eagain_count--;
+ if (eagain_count <= 0) {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED,
+ "Sending command failed: '%s'", strerror (errno));
+ break;
+ }
+ } else {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED,
+ "Sending command failed: '%s'", strerror (errno));
+ break;
+ }
+ } else
+ s++;
+
+ if (priv->send_delay)
+ usleep (priv->send_delay);
+ }
+
+ return *s == '\0';
+}
+
+typedef struct {
+ char *command;
+ MMSerialResponseFn callback;
+ gpointer user_data;
+ guint32 timeout;
+ gboolean cached;
+} MMQueueData;
+
+static void
+mm_serial_port_schedule_queue_process (MMSerialPort *self)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ GSource *source;
+
+ if (priv->timeout_id) {
+ /* A command is already in progress */
+ return;
+ }
+
+ if (priv->queue_schedule) {
+ /* Already scheduled */
+ return;
+ }
+
+ source = g_idle_source_new ();
+ g_source_set_closure (source, g_cclosure_new_object (G_CALLBACK (mm_serial_port_queue_process), G_OBJECT (self)));
+ g_source_attach (source, NULL);
+ priv->queue_schedule = g_source_get_id (source);
+ g_source_unref (source);
+}
+
+static void
+mm_serial_port_got_response (MMSerialPort *self, GError *error)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ MMQueueData *info;
+
+ if (priv->timeout_id) {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+
+ info = (MMQueueData *) g_queue_pop_head (priv->queue);
+ if (info) {
+ if (info->cached && !error)
+ mm_serial_port_set_cached_reply (self, info->command, priv->response->str);
+
+ if (info->callback)
+ info->callback (self, priv->response, error, info->user_data);
+
+ g_free (info->command);
+ g_slice_free (MMQueueData, info);
+ }
+
+ if (error)
+ g_error_free (error);
+
+ g_string_truncate (priv->response, 0);
+ if (!g_queue_is_empty (priv->queue))
+ mm_serial_port_schedule_queue_process (self);
+}
+
+static gboolean
+mm_serial_port_timed_out (gpointer data)
+{
+ MMSerialPort *self = MM_SERIAL_PORT (data);
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ GError *error;
+
+ priv->timeout_id = 0;
+
+ error = g_error_new_literal (MM_SERIAL_ERROR,
+ MM_SERIAL_RESPONSE_TIMEOUT,
+ "Serial command timed out");
+ /* FIXME: This is not completely correct - if the response finally arrives and there's
+ some other command waiting for response right now, the other command will
+ get the output of the timed out command. Not sure what to do here. */
+ mm_serial_port_got_response (self, error);
+
+ return FALSE;
+}
+
+static gboolean
+mm_serial_port_queue_process (gpointer data)
+{
+ MMSerialPort *self = MM_SERIAL_PORT (data);
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ MMQueueData *info;
+ GError *error = NULL;
+
+ priv->queue_schedule = 0;
+
+ info = (MMQueueData *) g_queue_peek_head (priv->queue);
+ if (!info)
+ return FALSE;
+
+ if (info->cached) {
+ const char *cached = mm_serial_port_get_cached_reply (self, info->command);
+
+ if (cached) {
+ g_string_append (priv->response, cached);
+ mm_serial_port_got_response (self, NULL);
+ return FALSE;
+ }
+ }
+
+ if (mm_serial_port_send_command (self, info->command, &error)) {
+ GSource *source;
+
+ source = g_timeout_source_new_seconds (info->timeout);
+ g_source_set_closure (source, g_cclosure_new_object (G_CALLBACK (mm_serial_port_timed_out), G_OBJECT (self)));
+ g_source_attach (source, NULL);
+ priv->timeout_id = g_source_get_id (source);
+ g_source_unref (source);
+ } else {
+ mm_serial_port_got_response (self, error);
+ }
+
+ return FALSE;
+}
+
+void
+mm_serial_port_add_unsolicited_msg_handler (MMSerialPort *self,
+ GRegex *regex,
+ MMSerialUnsolicitedMsgFn callback,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ MMUnsolicitedMsgHandler *handler;
+ MMSerialPortPrivate *priv;
+
+ g_return_if_fail (MM_IS_SERIAL_PORT (self));
+ g_return_if_fail (regex != NULL);
+
+ handler = g_slice_new (MMUnsolicitedMsgHandler);
+ handler->regex = g_regex_ref (regex);
+ handler->callback = callback;
+ handler->user_data = user_data;
+ handler->notify = notify;
+
+ priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ priv->unsolicited_msg_handlers = g_slist_append (priv->unsolicited_msg_handlers, handler);
+}
+
+void
+mm_serial_port_set_response_parser (MMSerialPort *self,
+ MMSerialResponseParserFn fn,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ g_return_if_fail (MM_IS_SERIAL_PORT (self));
+
+ if (priv->response_parser_notify)
+ priv->response_parser_notify (priv->response_parser_user_data);
+
+ priv->response_parser_fn = fn;
+ priv->response_parser_user_data = user_data;
+ priv->response_parser_notify = notify;
+}
+
+static gboolean
+remove_eval_cb (const GMatchInfo *match_info,
+ GString *result,
+ gpointer user_data)
+{
+ int *result_len = (int *) user_data;
+ int start;
+ int end;
+
+ if (g_match_info_fetch_pos (match_info, 0, &start, &end))
+ *result_len -= (end - start);
+
+ return FALSE;
+}
+
+static void
+parse_unsolicited_messages (MMSerialPort *self,
+ GString *response)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ GSList *iter;
+
+ for (iter = priv->unsolicited_msg_handlers; iter; iter = iter->next) {
+ MMUnsolicitedMsgHandler *handler = (MMUnsolicitedMsgHandler *) iter->data;
+ GMatchInfo *match_info;
+ gboolean matches;
+
+ matches = g_regex_match_full (handler->regex, response->str, response->len, 0, 0, &match_info, NULL);
+ if (handler->callback) {
+ while (g_match_info_matches (match_info)) {
+ handler->callback (self, match_info, handler->user_data);
+ g_match_info_next (match_info, NULL);
+ }
+ }
+
+ g_match_info_free (match_info);
+
+ if (matches) {
+ /* Remove matches */
+ char *str;
+ int result_len = response->len;
+
+ str = g_regex_replace_eval (handler->regex, response->str, response->len, 0, 0,
+ remove_eval_cb, &result_len, NULL);
+
+ g_string_truncate (response, 0);
+ g_string_append_len (response, str, result_len);
+ g_free (str);
+ }
+ }
+}
+
+static gboolean
+parse_response (MMSerialPort *self,
+ GString *response,
+ GError **error)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ g_return_val_if_fail (priv->response_parser_fn != NULL, FALSE);
+
+ parse_unsolicited_messages (self, response);
+
+ return priv->response_parser_fn (priv->response_parser_user_data, response, error);
+}
+
+static gboolean
+data_available (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ MMSerialPort *self = MM_SERIAL_PORT (data);
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ char buf[SERIAL_BUF_SIZE + 1];
+ gsize bytes_read;
+ GIOStatus status;
+
+ if (condition & G_IO_HUP) {
+ g_string_truncate (priv->response, 0);
+ mm_serial_port_close (self);
+ return FALSE;
+ }
+
+ if (condition & G_IO_ERR) {
+ g_string_truncate (priv->response, 0);
+ return TRUE;
+ }
+
+ do {
+ GError *err = NULL;
+
+ status = g_io_channel_read_chars (source, buf, SERIAL_BUF_SIZE, &bytes_read, &err);
+ if (status == G_IO_STATUS_ERROR) {
+ g_warning ("%s", err->message);
+ g_error_free (err);
+ err = NULL;
+ }
+
+ /* If no bytes read, just let g_io_channel wait for more data */
+ if (bytes_read == 0)
+ break;
+
+ if (bytes_read > 0) {
+ serial_debug (self, "<--", buf, bytes_read);
+ g_string_append_len (priv->response, buf, bytes_read);
+ }
+
+ /* Make sure the string doesn't grow too long */
+ if (priv->response->len > SERIAL_BUF_SIZE) {
+ g_warning ("%s (%s): response buffer filled before repsonse received",
+ G_STRFUNC, mm_port_get_device (MM_PORT (self)));
+ g_string_erase (priv->response, 0, (SERIAL_BUF_SIZE / 2));
+ }
+
+ if (parse_response (self, priv->response, &err))
+ mm_serial_port_got_response (self, err);
+ } while (bytes_read == SERIAL_BUF_SIZE || status == G_IO_STATUS_AGAIN);
+
+ return TRUE;
+}
+
+static void
+port_connected (MMSerialPort *self, GParamSpec *pspec, gpointer user_data)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ gboolean connected;
+
+ if (priv->fd < 0)
+ return;
+
+ /* When the port is connected, drop the serial port lock so PPP can do
+ * something with the port. When the port is disconnected, grab the lock
+ * again.
+ */
+ connected = mm_port_get_connected (MM_PORT (self));
+
+ if (ioctl (priv->fd, (connected ? TIOCNXCL : TIOCEXCL)) < 0) {
+ g_warning ("%s: (%s) could not %s serial port lock: (%d) %s",
+ __func__,
+ mm_port_get_device (MM_PORT (self)),
+ connected ? "drop" : "re-acquire",
+ errno,
+ strerror (errno));
+ if (!connected) {
+ // FIXME: do something here, maybe try again in a few seconds or
+ // close the port and error out?
+ }
+ }
+}
+
+gboolean
+mm_serial_port_open (MMSerialPort *self, GError **error)
+{
+ MMSerialPortPrivate *priv;
+ char *devfile;
+ const char *device;
+
+ g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE);
+
+ priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ if (priv->fd >= 0) {
+ /* Already open */
+ return TRUE;
+ }
+
+ device = mm_port_get_device (MM_PORT (self));
+
+ g_message ("(%s) opening serial device...", device);
+ devfile = g_strdup_printf ("/dev/%s", device);
+ errno = 0;
+ priv->fd = open (devfile, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY);
+ g_free (devfile);
+
+ if (priv->fd < 0) {
+ /* nozomi isn't ready yet when the port appears, and it'll return
+ * ENODEV when open(2) is called on it. Make sure we can handle this
+ * by returning a special error in that case.
+ */
+ g_set_error (error,
+ MM_SERIAL_ERROR,
+ (errno == ENODEV) ? MM_SERIAL_OPEN_FAILED_NO_DEVICE : MM_SERIAL_OPEN_FAILED,
+ "Could not open serial device %s: %s", device, strerror (errno));
+ return FALSE;
+ }
+
+ if (ioctl (priv->fd, TIOCEXCL) < 0) {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED,
+ "Could not lock serial device %s: %s", device, strerror (errno));
+ close (priv->fd);
+ priv->fd = -1;
+ return FALSE;
+ }
+
+ if (ioctl (priv->fd, TCGETA, &priv->old_t) < 0) {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED,
+ "Could not open serial device %s: %s", device, strerror (errno));
+ close (priv->fd);
+ priv->fd = -1;
+ return FALSE;
+ }
+
+ if (!config_fd (self, error)) {
+ close (priv->fd);
+ priv->fd = -1;
+ return FALSE;
+ }
+
+ 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,
+ G_IO_IN | G_IO_ERR | G_IO_HUP,
+ data_available, self);
+
+ g_warn_if_fail (priv->connected_id == 0);
+ priv->connected_id = g_signal_connect (self, "notify::" MM_PORT_CONNECTED,
+ G_CALLBACK (port_connected), NULL);
+
+ return TRUE;
+}
+
+void
+mm_serial_port_close (MMSerialPort *self)
+{
+ MMSerialPortPrivate *priv;
+
+ g_return_if_fail (MM_IS_SERIAL_PORT (self));
+
+ priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ if (priv->connected_id) {
+ g_signal_handler_disconnect (self, priv->connected_id);
+ priv->connected_id = 0;
+ }
+
+ if (priv->fd >= 0) {
+ g_message ("(%s) closing serial device...", mm_port_get_device (MM_PORT (self)));
+
+ mm_port_set_connected (MM_PORT (self), FALSE);
+
+ if (priv->channel) {
+ g_source_remove (priv->watch_id);
+ g_io_channel_shutdown (priv->channel, TRUE, NULL);
+ g_io_channel_unref (priv->channel);
+ priv->channel = NULL;
+ }
+
+ if (priv->flash_id > 0) {
+ g_source_remove (priv->flash_id);
+ priv->flash_id = 0;
+ }
+
+ ioctl (priv->fd, TCSETA, &priv->old_t);
+ close (priv->fd);
+ priv->fd = -1;
+ }
+}
+
+static void
+internal_queue_command (MMSerialPort *self,
+ const char *command,
+ gboolean cached,
+ guint32 timeout_seconds,
+ MMSerialResponseFn callback,
+ gpointer user_data)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ MMQueueData *info;
+
+ g_return_if_fail (MM_IS_SERIAL_PORT (self));
+ g_return_if_fail (command != NULL);
+
+ info = g_slice_new0 (MMQueueData);
+ info->command = g_strdup (command);
+ info->cached = cached;
+ info->timeout = timeout_seconds;
+ info->callback = callback;
+ info->user_data = user_data;
+
+ /* Clear the cached value for this command if not asking for cached value */
+ if (!cached)
+ mm_serial_port_set_cached_reply (self, command, NULL);
+
+ g_queue_push_tail (priv->queue, info);
+
+ if (g_queue_get_length (priv->queue) == 1)
+ mm_serial_port_schedule_queue_process (self);
+}
+
+void
+mm_serial_port_queue_command (MMSerialPort *self,
+ const char *command,
+ guint32 timeout_seconds,
+ MMSerialResponseFn callback,
+ gpointer user_data)
+{
+ internal_queue_command (self, command, FALSE, timeout_seconds, callback, user_data);
+}
+
+void
+mm_serial_port_queue_command_cached (MMSerialPort *self,
+ const char *command,
+ guint32 timeout_seconds,
+ MMSerialResponseFn callback,
+ gpointer user_data)
+{
+ internal_queue_command (self, command, TRUE, timeout_seconds, callback, user_data);
+}
+
+typedef struct {
+ MMSerialPort *port;
+ speed_t current_speed;
+ MMSerialFlashFn callback;
+ gpointer user_data;
+} FlashInfo;
+
+static gboolean
+get_speed (MMSerialPort *self, speed_t *speed, GError **error)
+{
+ struct termios options;
+
+ memset (&options, 0, sizeof (struct termios));
+ if (tcgetattr (MM_SERIAL_PORT_GET_PRIVATE (self)->fd, &options) != 0) {
+ g_set_error (error,
+ MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "%s: tcgetattr() error %d",
+ __func__, errno);
+ return FALSE;
+ }
+
+ *speed = cfgetospeed (&options);
+ return TRUE;
+}
+
+static gboolean
+set_speed (MMSerialPort *self, speed_t speed, GError **error)
+{
+ struct termios options;
+ int fd, count = 4;
+ gboolean success = FALSE;
+
+ fd = MM_SERIAL_PORT_GET_PRIVATE (self)->fd;
+
+ memset (&options, 0, sizeof (struct termios));
+ if (tcgetattr (fd, &options) != 0) {
+ g_set_error (error,
+ MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "%s: tcgetattr() error %d",
+ __func__, errno);
+ return FALSE;
+ }
+
+ cfsetispeed (&options, speed);
+ cfsetospeed (&options, speed);
+ options.c_cflag |= (CLOCAL | CREAD);
+
+ while (count-- > 0) {
+ if (tcsetattr (fd, TCSANOW, &options) == 0) {
+ success = TRUE;
+ break; /* Operation successful */
+ }
+
+ /* Try a few times if EAGAIN */
+ if (errno == EAGAIN)
+ g_usleep (100000);
+ else {
+ /* If not EAGAIN, hard error */
+ g_set_error (error,
+ MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "%s: tcsetattr() error %d",
+ __func__, errno);
+ return FALSE;
+ }
+ }
+
+ if (!success) {
+ g_set_error (error,
+ MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "%s: tcsetattr() retry timeout",
+ __func__);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+flash_do (gpointer data)
+{
+ FlashInfo *info = (FlashInfo *) data;
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (info->port);
+ GError *error = NULL;
+
+ priv->flash_id = 0;
+
+ if (!set_speed (info->port, info->current_speed, &error))
+ g_assert (error);
+
+ info->callback (info->port, error, info->user_data);
+ g_clear_error (&error);
+ g_slice_free (FlashInfo, info);
+ return FALSE;
+}
+
+gboolean
+mm_serial_port_flash (MMSerialPort *self,
+ guint32 flash_time,
+ MMSerialFlashFn callback,
+ gpointer user_data)
+{
+ FlashInfo *info;
+ MMSerialPortPrivate *priv;
+ speed_t cur_speed = 0;
+ GError *error = NULL;
+
+ g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE);
+ g_return_val_if_fail (callback != NULL, FALSE);
+
+ priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ if (priv->flash_id > 0) {
+ error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
+ "Modem is already being flashed.");
+ callback (self, error, user_data);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ if (!get_speed (self, &cur_speed, &error)) {
+ callback (self, error, user_data);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ info = g_slice_new0 (FlashInfo);
+ info->port = self;
+ info->current_speed = cur_speed;
+ info->callback = callback;
+ info->user_data = user_data;
+
+ if (!set_speed (self, B0, &error)) {
+ callback (self, error, user_data);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ priv->flash_id = g_timeout_add (flash_time, flash_do, info);
+ return TRUE;
+}
+
+void
+mm_serial_port_flash_cancel (MMSerialPort *self)
+{
+ MMSerialPortPrivate *priv;
+
+ g_return_if_fail (MM_IS_SERIAL_PORT (self));
+
+ priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ if (priv->flash_id > 0) {
+ g_source_remove (priv->flash_id);
+ priv->flash_id = 0;
+ }
+}
+
+/*****************************************************************************/
+
+MMSerialPort *
+mm_serial_port_new (const char *name, MMPortType ptype)
+{
+ return MM_SERIAL_PORT (g_object_new (MM_TYPE_SERIAL_PORT,
+ MM_PORT_DEVICE, name,
+ MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY,
+ MM_PORT_TYPE, ptype,
+ NULL));
+}
+
+static void
+mm_serial_port_init (MMSerialPort *self)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ priv->reply_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ priv->fd = -1;
+ priv->baud = 57600;
+ priv->bits = 8;
+ priv->parity = 'n';
+ priv->stopbits = 1;
+ priv->send_delay = 1000;
+
+ priv->queue = g_queue_new ();
+ priv->command = g_string_new_len ("AT", SERIAL_BUF_SIZE);
+ priv->response = g_string_sized_new (SERIAL_BUF_SIZE);
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case PROP_BAUD:
+ priv->baud = g_value_get_uint (value);
+ break;
+ case PROP_BITS:
+ priv->bits = g_value_get_uint (value);
+ break;
+ case PROP_PARITY:
+ priv->parity = g_value_get_char (value);
+ break;
+ case PROP_STOPBITS:
+ priv->stopbits = g_value_get_uint (value);
+ break;
+ case PROP_SEND_DELAY:
+ priv->send_delay = g_value_get_uint64 (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case PROP_BAUD:
+ g_value_set_uint (value, priv->baud);
+ break;
+ case PROP_BITS:
+ g_value_set_uint (value, priv->bits);
+ break;
+ case PROP_PARITY:
+ g_value_set_char (value, priv->parity);
+ break;
+ case PROP_STOPBITS:
+ g_value_set_uint (value, priv->stopbits);
+ break;
+ case PROP_SEND_DELAY:
+ g_value_set_uint64 (value, priv->send_delay);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ mm_serial_port_close (MM_SERIAL_PORT (object));
+
+ G_OBJECT_CLASS (mm_serial_port_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMSerialPort *self = MM_SERIAL_PORT (object);
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ g_hash_table_destroy (priv->reply_cache);
+ g_queue_free (priv->queue);
+ g_string_free (priv->command, TRUE);
+ g_string_free (priv->response, TRUE);
+
+ while (priv->unsolicited_msg_handlers) {
+ MMUnsolicitedMsgHandler *handler = (MMUnsolicitedMsgHandler *) priv->unsolicited_msg_handlers->data;
+
+ if (handler->notify)
+ handler->notify (handler->user_data);
+
+ g_regex_unref (handler->regex);
+ g_slice_free (MMUnsolicitedMsgHandler, handler);
+ priv->unsolicited_msg_handlers = g_slist_delete_link (priv->unsolicited_msg_handlers,
+ priv->unsolicited_msg_handlers);
+ }
+
+ if (priv->response_parser_notify)
+ priv->response_parser_notify (priv->response_parser_user_data);
+
+ G_OBJECT_CLASS (mm_serial_port_parent_class)->finalize (object);
+}
+
+static void
+mm_serial_port_class_init (MMSerialPortClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMSerialPortPrivate));
+
+ /* Virtual methods */
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ /* Properties */
+ g_object_class_install_property
+ (object_class, PROP_BAUD,
+ g_param_spec_uint (MM_SERIAL_PORT_BAUD,
+ "Baud",
+ "Baud rate",
+ 0, G_MAXUINT, 57600,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_BITS,
+ g_param_spec_uint (MM_SERIAL_PORT_BITS,
+ "Bits",
+ "Bits",
+ 5, 8, 8,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_PARITY,
+ g_param_spec_char (MM_SERIAL_PORT_PARITY,
+ "Parity",
+ "Parity",
+ 'E', 'o', 'n',
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_STOPBITS,
+ g_param_spec_uint (MM_SERIAL_PORT_STOPBITS,
+ "Stopbits",
+ "Stopbits",
+ 1, 2, 1,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_SEND_DELAY,
+ g_param_spec_uint64 (MM_SERIAL_PORT_SEND_DELAY,
+ "SendDelay",
+ "Send delay",
+ 0, G_MAXUINT64, 0,
+ G_PARAM_READWRITE));
+}
diff --git a/src/mm-serial-port.h b/src/mm-serial-port.h
new file mode 100644
index 0000000..841b4fa
--- /dev/null
+++ b/src/mm-serial-port.h
@@ -0,0 +1,105 @@
+/* -*- 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 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_SERIAL_PORT_H
+#define MM_SERIAL_PORT_H
+
+#include <glib.h>
+#include <glib/gtypes.h>
+#include <glib-object.h>
+
+#include "mm-port.h"
+
+#define MM_TYPE_SERIAL_PORT (mm_serial_port_get_type ())
+#define MM_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SERIAL_PORT, MMSerialPort))
+#define MM_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SERIAL_PORT, MMSerialPortClass))
+#define MM_IS_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SERIAL_PORT))
+#define MM_IS_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SERIAL_PORT))
+#define MM_SERIAL_PORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SERIAL_PORT, MMSerialPortClass))
+
+#define MM_SERIAL_PORT_BAUD "baud"
+#define MM_SERIAL_PORT_BITS "bits"
+#define MM_SERIAL_PORT_PARITY "parity"
+#define MM_SERIAL_PORT_STOPBITS "stopbits"
+#define MM_SERIAL_PORT_SEND_DELAY "send-delay"
+
+typedef struct _MMSerialPort MMSerialPort;
+typedef struct _MMSerialPortClass MMSerialPortClass;
+
+typedef gboolean (*MMSerialResponseParserFn) (gpointer user_data,
+ GString *response,
+ GError **error);
+
+typedef void (*MMSerialUnsolicitedMsgFn) (MMSerialPort *port,
+ GMatchInfo *match_info,
+ gpointer user_data);
+
+typedef void (*MMSerialResponseFn) (MMSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data);
+
+typedef void (*MMSerialFlashFn) (MMSerialPort *port,
+ GError *error,
+ gpointer user_data);
+
+struct _MMSerialPort {
+ MMPort parent;
+};
+
+struct _MMSerialPortClass {
+ MMPortClass parent;
+};
+
+GType mm_serial_port_get_type (void);
+
+MMSerialPort *mm_serial_port_new (const char *name, MMPortType ptype);
+
+void mm_serial_port_add_unsolicited_msg_handler (MMSerialPort *self,
+ GRegex *regex,
+ MMSerialUnsolicitedMsgFn callback,
+ gpointer user_data,
+ GDestroyNotify notify);
+
+void mm_serial_port_set_response_parser (MMSerialPort *self,
+ MMSerialResponseParserFn fn,
+ gpointer user_data,
+ GDestroyNotify notify);
+
+gboolean mm_serial_port_open (MMSerialPort *self,
+ GError **error);
+
+void mm_serial_port_close (MMSerialPort *self);
+void mm_serial_port_queue_command (MMSerialPort *self,
+ const char *command,
+ guint32 timeout_seconds,
+ MMSerialResponseFn callback,
+ gpointer user_data);
+
+void mm_serial_port_queue_command_cached (MMSerialPort *self,
+ const char *command,
+ guint32 timeout_seconds,
+ MMSerialResponseFn callback,
+ gpointer user_data);
+
+gboolean mm_serial_port_flash (MMSerialPort *self,
+ guint32 flash_time,
+ MMSerialFlashFn callback,
+ gpointer user_data);
+void mm_serial_port_flash_cancel (MMSerialPort *self);
+
+#endif /* MM_SERIAL_PORT_H */
+
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
new file mode 100644
index 0000000..74255db
--- /dev/null
+++ b/src/tests/Makefile.am
@@ -0,0 +1,22 @@
+INCLUDES = \
+ -I$(top_srcdir)/src
+
+noinst_PROGRAMS = test-modem-helpers
+
+test_modem_helpers_SOURCES = \
+ test-modem-helpers.c
+
+test_modem_helpers_CPPFLAGS = \
+ $(MM_CFLAGS)
+
+test_modem_helpers_LDADD = \
+ $(top_builddir)/src/libmodem-helpers.la \
+ $(MM_LIBS)
+
+if WITH_TESTS
+
+check-local: test-modem-helpers
+ $(abs_builddir)/test-modem-helpers
+
+endif
+
diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c
new file mode 100644
index 0000000..3d93423
--- /dev/null
+++ b/src/tests/test-modem-helpers.c
@@ -0,0 +1,479 @@
+/* -*- 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"
+
+#define MM_SCAN_TAG_STATUS "status"
+#define MM_SCAN_TAG_OPER_LONG "operator-long"
+#define MM_SCAN_TAG_OPER_SHORT "operator-short"
+#define MM_SCAN_TAG_OPER_NUM "operator-num"
+#define MM_SCAN_TAG_ACCESS_TECH "access-tech"
+
+typedef struct {
+ const char *status;
+ const char *oper_long;
+ const char *oper_short;
+ const char *oper_num;
+ const char *tech;
+} OperEntry;
+
+#define ARRAY_LEN(i) (sizeof (i) / sizeof (i[0]))
+
+static void
+test_results (const char *desc,
+ const char *reply,
+ OperEntry *expected_results,
+ guint32 expected_results_len)
+{
+ guint i;
+ GError *error = NULL;
+ GPtrArray *results;
+
+ g_print ("\nTesting %s +COPS response...\n", desc);
+
+ results = mm_gsm_parse_scan_response (reply, &error);
+ g_assert (results);
+ g_assert (error == NULL);
+
+ g_assert (results->len == expected_results_len);
+
+ for (i = 0; i < results->len; i++) {
+ GHashTable *entry = g_ptr_array_index (results, i);
+ const char *value;
+ OperEntry *expected = &expected_results[i];
+
+ value = g_hash_table_lookup (entry, MM_SCAN_TAG_STATUS);
+ g_assert (value);
+ g_assert (strcmp (value, expected->status) == 0);
+
+ value = g_hash_table_lookup (entry, MM_SCAN_TAG_OPER_LONG);
+ if (expected->oper_long) {
+ g_assert (value);
+ g_assert (strcmp (value, expected->oper_long) == 0);
+ } else
+ g_assert (value == NULL);
+
+ value = g_hash_table_lookup (entry, MM_SCAN_TAG_OPER_SHORT);
+ if (expected->oper_short) {
+ g_assert (value);
+ g_assert (strcmp (value, expected->oper_short) == 0);
+ } else
+ g_assert (value == NULL);
+
+ value = g_hash_table_lookup (entry, MM_SCAN_TAG_OPER_NUM);
+ g_assert (expected->oper_num);
+ g_assert (value);
+ g_assert (strcmp (value, expected->oper_num) == 0);
+
+ value = g_hash_table_lookup (entry, MM_SCAN_TAG_ACCESS_TECH);
+ if (expected->tech) {
+ g_assert (value);
+ g_assert (strcmp (value, expected->tech) == 0);
+ } else
+ g_assert (value == NULL);
+ }
+
+ mm_gsm_destroy_scan_data (results);
+}
+
+static void
+test_cops_response_tm506 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"\",\"T-Mobile\",\"31026\",0),(2,\"T - Mobile\",\"T - Mobile\",\"310260\"),2),(1,\"AT&T\",\"AT&T\",\"310410\"),0)";
+ static OperEntry expected[] = {
+ { "2", NULL, "T-Mobile", "31026", "0" },
+ { "2", "T - Mobile", "T - Mobile", "310260", "2" },
+ { "1", "AT&T", "AT&T", "310410", "0" }
+ };
+
+ test_results ("TM-506", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_gt3gplus (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (1,\"T-Mobile US\",\"TMO US\",\"31026\",0),(1,\"Cingular\",\"Cingular\",\"310410\",0),,(0, 1, 3),(0-2)";
+ static OperEntry expected[] = {
+ { "1", "T-Mobile US", "TMO US", "31026", "0" },
+ { "1", "Cingular", "Cingular", "310410", "0" },
+ };
+
+ test_results ("GlobeTrotter 3G+ (nozomi)", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_ac881 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (1,\"T-Mobile\",\"TMO\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),)";
+ static OperEntry expected[] = {
+ { "1", "T-Mobile", "TMO", "31026", "0" },
+ { "1", "AT&T", "AT&T", "310410", "2" },
+ { "1", "AT&T", "AT&T", "310410", "0" },
+ };
+
+ test_results ("Sierra AirCard 881", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_gtmax36 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T-Mobile US\",\"TMO US\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0, 1,)";
+ static OperEntry expected[] = {
+ { "2", "T-Mobile US", "TMO US", "31026", "0" },
+ { "1", "AT&T", "AT&T", "310410", "2" },
+ { "1", "AT&T", "AT&T", "310410", "0" },
+ };
+
+ test_results ("Option GlobeTrotter MAX 3.6", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_ac860 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T-Mobile\",\"TMO\",\"31026\",0),(1,\"Cingular\",\"Cinglr\",\"310410\",2),(1,\"Cingular\",\"Cinglr\",\"310410\",0),,)";
+ static OperEntry expected[] = {
+ { "2", "T-Mobile", "TMO", "31026", "0" },
+ { "1", "Cingular", "Cinglr", "310410", "2" },
+ { "1", "Cingular", "Cinglr", "310410", "0" },
+ };
+
+ test_results ("Sierra AirCard 860", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_gtm378 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T-Mobile\",\"T-Mobile\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0, 1, 3),(0-2)";
+ static OperEntry expected[] = {
+ { "2", "T-Mobile", "T-Mobile", "31026", "0" },
+ { "1", "AT&T", "AT&T", "310410", "2" },
+ { "1", "AT&T", "AT&T", "310410", "0" },
+ };
+
+ test_results ("Option GTM378", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_motoc (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T-Mobile\",\"\",\"310260\"),(0,\"Cingular Wireless\",\"\",\"310410\")";
+ static OperEntry expected[] = {
+ { "2", "T-Mobile", NULL, "310260", NULL },
+ { "0", "Cingular Wireless", NULL, "310410", NULL },
+ };
+
+ test_results ("BUSlink SCWi275u (Motorola C-series)", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_mf627a (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"AT&T@\",\"AT&TD\",\"310410\",0),(3,\"Voicestream Wireless Corporation\",\"VSTREAM\",\"31026\",0),";
+ static OperEntry expected[] = {
+ { "2", "AT&T@", "AT&TD", "310410", "0" },
+ { "3", "Voicestream Wireless Corporation", "VSTREAM", "31026", "0" },
+ };
+
+ test_results ("ZTE MF627 (A)", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_mf627b (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"AT&Tp\",\"AT&T@\",\"310410\",0),(3,\"\",\"\",\"31026\",0),";
+ static OperEntry expected[] = {
+ { "2", "AT&Tp", "AT&T@", "310410", "0" },
+ { "3", NULL, NULL, "31026", "0" },
+ };
+
+ test_results ("ZTE MF627 (B)", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_e160g (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T-Mobile\",\"TMO\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)";
+ static OperEntry expected[] = {
+ { "2", "T-Mobile", "TMO", "31026", "0" },
+ { "1", "AT&T", "AT&T", "310410", "0" },
+ };
+
+ test_results ("Huawei E160G", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_mercury (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"\",\"\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),(1,\"T-Mobile\",\"TMO\",\"31026\",0),,(0,1,2,3,4),(0,1,2)";
+ static OperEntry expected[] = {
+ { "2", NULL, NULL, "310410", "2" },
+ { "1", "AT&T", "AT&T", "310410", "0" },
+ { "1", "T-Mobile", "TMO", "31026", "0" },
+ };
+
+ test_results ("Sierra AT&T USBConnect Mercury", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_quicksilver (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"AT&T\",\"\",\"310410\",0),(2,\"\",\"\",\"3104100\",2),(1,\"AT&T\",\"\",\"310260\",0),,(0-4),(0-2)";
+ static OperEntry expected[] = {
+ { "2", "AT&T", NULL, "310410", "0" },
+ { "2", NULL, NULL, "3104100", "2" },
+ { "1", "AT&T", NULL, "310260", "0" },
+ };
+
+ test_results ("Option AT&T Quicksilver", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_icon225 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T-Mobile US\",\"TMO US\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0, 1, 3),(0-2)";
+ static OperEntry expected[] = {
+ { "2", "T-Mobile US", "TMO US", "31026", "0" },
+ { "1", "AT&T", "AT&T", "310410", "0" },
+ };
+
+ test_results ("Option iCON 225", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_icon452 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (1,\"T-Mobile US\",\"TMO US\",\"31026\",0),(2,\"T-Mobile\",\"T-Mobile\",\"310260\",2),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)";
+ static OperEntry expected[] = {
+ { "1", "T-Mobile US", "TMO US", "31026", "0" },
+ { "2", "T-Mobile", "T-Mobile", "310260", "2" },
+ { "1", "AT&T", "AT&T", "310410", "2" },
+ { "1", "AT&T", "AT&T", "310410", "0" }
+ };
+
+ test_results ("Option iCON 452", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_f3507g (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T - Mobile\",\"T - Mobile\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2)";
+ static OperEntry expected[] = {
+ { "2", "T - Mobile", "T - Mobile", "31026", "0" },
+ { "1", "AT&T", "AT&T", "310410", "0" },
+ { "1", "AT&T", "AT&T", "310410", "2" }
+ };
+
+ test_results ("Ericsson F3507g", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_f3607gw (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T - Mobile\",\"T - Mobile\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\"),2),(1,\"AT&T\",\"AT&T\",\"310410\"),0)";
+ static OperEntry expected[] = {
+ { "2", "T - Mobile", "T - Mobile", "31026", "0" },
+ { "1", "AT&T", "AT&T", "310410", "2" },
+ { "1", "AT&T", "AT&T", "310410", "0" }
+ };
+
+ test_results ("Ericsson F3607gw", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_mc8775 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T-Mobile\",\"T-Mobile\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)";
+ static OperEntry expected[] = {
+ { "2", "T-Mobile", "T-Mobile", "31026", "0" },
+ { "1", "AT&T", "AT&T", "310410", "2" },
+ { "1", "AT&T", "AT&T", "310410", "0" }
+ };
+
+ test_results ("Sierra MC8775", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_n80 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T - Mobile\",,\"31026\"),(1,\"Einstein PCS\",,\"31064\"),(1,\"Cingular\",,\"31041\"),,(0,1,3),(0,2)";
+ static OperEntry expected[] = {
+ { "2", "T - Mobile", NULL, "31026", NULL },
+ { "1", "Einstein PCS", NULL, "31064", NULL },
+ { "1", "Cingular", NULL, "31041", NULL },
+ };
+
+ test_results ("Nokia N80", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_e1550 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T-Mobile\",\"TMO\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)";
+ static OperEntry expected[] = {
+ { "2", "T-Mobile", "TMO", "31026", "0" },
+ { "1", "AT&T", "AT&T", "310410", "0" },
+ };
+
+ test_results ("Huawei E1550", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_mf622 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T-Mobile\",\"T-Mobile\",\"31026\",0),(1,\"\",\"\",\"310410\",0),";
+ static OperEntry expected[] = {
+ { "2", "T-Mobile", "T-Mobile", "31026", "0" },
+ { "1", NULL, NULL, "310410", "0" },
+ };
+
+ test_results ("ZTE MF622", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_e226 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (1,\"\",\"\",\"31026\",0),(1,\"\",\"\",\"310410\",2),(1,\"\",\"\",\"310410\",0),,(0,1,3,4),(0,1,2)";
+ static OperEntry expected[] = {
+ { "1", NULL, NULL, "31026", "0" },
+ { "1", NULL, NULL, "310410", "2" },
+ { "1", NULL, NULL, "310410", "0" },
+ };
+
+ test_results ("Huawei E226", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_xu870 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (0,\"AT&T MicroCell\",\"AT&T MicroCell\",\"310410\",2)\r\n+COPS: (1,\"AT&T MicroCell\",\"AT&T MicroCell\",\"310410\",0)\r\n+COPS: (1,\"T-Mobile\",\"TMO\",\"31026\",0)\r\n";
+ static OperEntry expected[] = {
+ { "0", "AT&T MicroCell", "AT&T MicroCell", "310410", "2" },
+ { "1", "AT&T MicroCell", "AT&T MicroCell", "310410", "0" },
+ { "1", "T-Mobile", "TMO", "31026", "0" },
+ };
+
+ test_results ("Novatel XU870", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_gtultraexpress (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T-Mobile US\",\"TMO US\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)";
+ static OperEntry expected[] = {
+ { "2", "T-Mobile US", "TMO US", "31026", "0" },
+ { "1", "AT&T", "AT&T", "310410", "2" },
+ { "1", "AT&T", "AT&T", "310410", "0" },
+ };
+
+ test_results ("Option GlobeTrotter Ultra Express", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_n2720 (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T - Mobile\",,\"31026\",0),\r\n(1,\"AT&T\",,\"310410\",0),,(0,1,3),(0,2)";
+ static OperEntry expected[] = {
+ { "2", "T - Mobile", NULL, "31026", "0" },
+ { "1", "AT&T", NULL, "310410", "0" },
+ };
+
+ test_results ("Nokia 2720", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_gobi (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (2,\"T-Mobile\",\"T-Mobile\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)";
+ static OperEntry expected[] = {
+ { "2", "T-Mobile", "T-Mobile", "31026", "0" },
+ { "1", "AT&T", "AT&T", "310410", "2" },
+ { "1", "AT&T", "AT&T", "310410", "0" },
+ };
+
+ test_results ("Qualcomm Gobi", reply, &expected[0], ARRAY_LEN (expected));
+}
+
+static void
+test_cops_response_gsm_invalid (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (0,1,2,3),(1,2,3,4)";
+ GPtrArray *results;
+ GError *error = NULL;
+
+ results = mm_gsm_parse_scan_response (reply, &error);
+ g_assert (results != NULL);
+ g_assert (error == NULL);
+}
+
+static void
+test_cops_response_umts_invalid (void *f, gpointer d)
+{
+ const char *reply = "+COPS: (0,1,2,3,4),(1,2,3,4,5)";
+ GPtrArray *results;
+ GError *error = NULL;
+
+ results = mm_gsm_parse_scan_response (reply, &error);
+ g_assert (results != NULL);
+ g_assert (error == NULL);
+}
+
+
+typedef void (*TCFunc)(void);
+
+#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (TCFunc) t, NULL)
+
+int main (int argc, char **argv)
+{
+ GTestSuite *suite;
+
+ g_test_init (&argc, &argv, NULL);
+
+ suite = g_test_get_root ();
+
+ g_test_suite_add (suite, TESTCASE (test_cops_response_tm506, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_gt3gplus, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_ac881, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_gtmax36, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_ac860, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_gtm378, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_motoc, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_mf627a, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_mf627b, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_e160g, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_mercury, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_quicksilver, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_icon225, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_icon452, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_f3507g, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_f3607gw, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_mc8775, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_n80, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_e1550, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_mf622, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_e226, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_xu870, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_gtultraexpress, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_n2720, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_gobi, NULL));
+
+ g_test_suite_add (suite, TESTCASE (test_cops_response_gsm_invalid, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cops_response_umts_invalid, NULL));
+
+ return g_test_run ();
+}
+