summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2016-06-28 18:22:24 +0200
committerThomas Haller <thaller@redhat.com>2016-06-28 18:22:24 +0200
commit88c305000067339fedf1bc0cbcd7cdf3082af491 (patch)
treecc1c38a3bee30311410225bc1d9be8cfedca46e0
parent88637dee661a3eb7e2e44fcb92ff33a4eccc9604 (diff)
parent2b958dce91a1799a93d002d8b2b22748e66dd9d7 (diff)
downloadNetworkManager-88c305000067339fedf1bc0cbcd7cdf3082af491.tar.gz
wwan/ofono: merge branch 'th/review/ofono'
https://mail.gnome.org/archives/networkmanager-list/2016-June/msg00085.html https://mail.gnome.org/archives/networkmanager-list/2016-June/msg00089.html
-rw-r--r--configure.ac10
-rw-r--r--src/devices/wwan/Makefile.am7
-rw-r--r--src/devices/wwan/nm-modem-broadband.c2
-rw-r--r--src/devices/wwan/nm-modem-broadband.h2
-rw-r--r--src/devices/wwan/nm-modem-manager.c362
-rw-r--r--src/devices/wwan/nm-modem-ofono.c1202
-rw-r--r--src/devices/wwan/nm-modem-ofono.h53
-rw-r--r--src/devices/wwan/nm-modem.c8
8 files changed, 1534 insertions, 112 deletions
diff --git a/configure.ac b/configure.ac
index e93975b15c..d795634b29 100644
--- a/configure.ac
+++ b/configure.ac
@@ -734,6 +734,15 @@ else
fi
AM_CONDITIONAL(WITH_BLUEZ5_DUN, test "${enable_bluez5_dun}" = "yes")
+# OFONO
+AC_ARG_WITH(ofono, AS_HELP_STRING([--with-ofono], [Enable oFono support (experimental)]),,[with_ofono=no])
+if (test "${with_ofono}" = "yes"); then
+ AC_DEFINE(WITH_OFONO, 1, [Define if you have oFono support (experimental)])
+else
+ AC_DEFINE(WITH_OFONO, 0, [Define if you have oFono support (experimental)])
+fi
+AM_CONDITIONAL(WITH_OFONO, test "${with_ofono}" = "yes")
+
# DHCP client support
AC_ARG_WITH([dhclient], AS_HELP_STRING([--with-dhclient=yes|no|path], [Enable dhclient 4.x support]))
AC_ARG_WITH([dhcpcd], AS_HELP_STRING([--with-dhcpcd=yes|no|path], [Enable dhcpcd 4.x support]))
@@ -1235,6 +1244,7 @@ echo " wext: $ac_with_wext"
echo " wifi: $enable_wifi"
echo " ppp: $enable_ppp"
echo " modemmanager-1: $with_modem_manager_1"
+echo " ofono: $with_ofono"
echo " concheck: $enable_concheck"
echo " libteamdctl: $enable_teamdctl"
echo " libnm-glib: $with_libnm_glib"
diff --git a/src/devices/wwan/Makefile.am b/src/devices/wwan/Makefile.am
index 95e8ec8d60..1d3c7c851d 100644
--- a/src/devices/wwan/Makefile.am
+++ b/src/devices/wwan/Makefile.am
@@ -42,6 +42,13 @@ libnm_wwan_la_SOURCES = \
\
$(GLIB_GENERATED)
+if WITH_OFONO
+libnm_wwan_la_SOURCES += \
+ nm-modem-ofono.c \
+ nm-modem-ofono.h \
+ $(NULL)
+endif
+
WWAN_SYMBOL_VIS_FILE=$(srcdir)/wwan-exports.ver
libnm_wwan_la_LDFLAGS = \
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 105cf4ddf3..e72b75923c 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -30,6 +30,8 @@
#include "nm-device-private.h"
#include "nm-platform.h"
+#define NM_MODEM_BROADBAND_MODEM "modem"
+
G_DEFINE_TYPE (NMModemBroadband, nm_modem_broadband, NM_TYPE_MODEM)
typedef enum {
diff --git a/src/devices/wwan/nm-modem-broadband.h b/src/devices/wwan/nm-modem-broadband.h
index a54e63a697..371607782b 100644
--- a/src/devices/wwan/nm-modem-broadband.h
+++ b/src/devices/wwan/nm-modem-broadband.h
@@ -32,8 +32,6 @@ G_BEGIN_DECLS
#define NM_IS_MODEM_BROADBAND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_MODEM_BROADBAND))
#define NM_MODEM_BROADBAND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_MODEM_BROADBAND, NMModemBroadbandClass))
-#define NM_MODEM_BROADBAND_MODEM "modem"
-
typedef struct _NMModemBroadband NMModemBroadband;
typedef struct _NMModemBroadbandClass NMModemBroadbandClass;
typedef struct _NMModemBroadbandPrivate NMModemBroadbandPrivate;
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index b159d1f8d2..964485fc34 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -17,26 +17,30 @@
*
* Copyright (C) 2009 - 2014 Red Hat, Inc.
* Copyright (C) 2009 Novell, Inc.
- * Copyright (C) 2009 Canonical Ltd.
+ * Copyright (C) 2009 - 2013 Canonical Ltd.
*/
#include "nm-default.h"
-#include <string.h>
+#include "nm-modem-manager.h"
+#include <string.h>
#include <libmm-glib.h>
-#include "nm-modem-manager.h"
-#include "nm-dbus-compat.h"
-#include "nm-modem.h"
-#include "nm-modem-broadband.h"
-
#if HAVE_LIBSYSTEMD
#include <systemd/sd-daemon.h>
#else
#define sd_booted() FALSE
#endif
+#include "nm-dbus-compat.h"
+#include "nm-modem.h"
+#include "nm-modem-broadband.h"
+
+#if WITH_OFONO
+#include "nm-modem-ofono.h"
+#endif
+
#define MODEM_POKE_INTERVAL 120
G_DEFINE_TYPE (NMModemManager, nm_modem_manager, G_TYPE_OBJECT)
@@ -45,9 +49,13 @@ struct _NMModemManagerPrivate {
GDBusConnection *dbus_connection;
MMManager *modem_manager;
guint mm_launch_id;
- guint mm_name_owner_changed_id;
- guint mm_object_added_id;
- guint mm_object_removed_id;
+ gulong mm_name_owner_changed_id;
+ gulong mm_object_added_id;
+ gulong mm_object_removed_id;
+
+#if WITH_OFONO
+ GDBusProxy *ofono_proxy;
+#endif
/* Common */
GHashTable *modems;
@@ -85,34 +93,14 @@ remove_one_modem (gpointer key, gpointer value, gpointer user_data)
}
static void
-modem_manager_clear_signals (NMModemManager *self)
+clear_modem_manager (NMModemManager *self)
{
if (!self->priv->modem_manager)
return;
-
- if (self->priv->mm_name_owner_changed_id) {
- if (g_signal_handler_is_connected (self->priv->modem_manager,
- self->priv->mm_name_owner_changed_id))
- g_signal_handler_disconnect (self->priv->modem_manager,
- self->priv->mm_name_owner_changed_id);
- self->priv->mm_name_owner_changed_id = 0;
- }
-
- if (self->priv->mm_object_added_id) {
- if (g_signal_handler_is_connected (self->priv->modem_manager,
- self->priv->mm_object_added_id))
- g_signal_handler_disconnect (self->priv->modem_manager,
- self->priv->mm_object_added_id);
- self->priv->mm_object_added_id = 0;
- }
-
- if (self->priv->mm_object_removed_id) {
- if (g_signal_handler_is_connected (self->priv->modem_manager,
- self->priv->mm_object_removed_id))
- g_signal_handler_disconnect (self->priv->modem_manager,
- self->priv->mm_object_removed_id);
- self->priv->mm_object_removed_id = 0;
- }
+ nm_clear_g_signal_handler (self->priv->modem_manager, &self->priv->mm_name_owner_changed_id);
+ nm_clear_g_signal_handler (self->priv->modem_manager, &self->priv->mm_object_added_id);
+ nm_clear_g_signal_handler (self->priv->modem_manager, &self->priv->mm_object_removed_id);
+ g_clear_object (&self->priv->modem_manager);
}
static void
@@ -189,7 +177,7 @@ modem_manager_available (NMModemManager *self)
static void schedule_modem_manager_relaunch (NMModemManager *self,
guint n_seconds);
-static void ensure_client (NMModemManager *self);
+static void ensure_modem_manager (NMModemManager *self);
static void
modem_manager_name_owner_changed (MMManager *modem_manager,
@@ -219,15 +207,174 @@ modem_manager_name_owner_changed (MMManager *modem_manager,
* nor 'object-removed' if it was created while there was no ModemManager in
* the bus. This hack avoids this issue until we get a GIO with the fix
* included... */
- modem_manager_clear_signals (self);
- g_clear_object (&self->priv->modem_manager);
- ensure_client (self);
+ clear_modem_manager (self);
+ ensure_modem_manager (self);
/* Whenever GDBusObjectManagerClient is fixed, we can just do the following:
* modem_manager_available (self);
*/
}
+#if WITH_OFONO
+static void
+ofono_create_modem (NMModemManager *self, const char *path)
+{
+ NMModem *modem = NULL;
+
+ /* Ensure duplicate modems aren't created. Because we're not using the
+ * ObjectManager interface there's a race during oFono startup where we
+ * receive ModemAdded signals before GetModems() returns, so some of the
+ * modems returned from GetModems() may already have been created.
+ */
+ if (!g_hash_table_lookup (self->priv->modems, path)) {
+ modem = nm_modem_ofono_new (path);
+ if (modem)
+ handle_new_modem (self, modem);
+ else
+ nm_log_warn (LOGD_MB, "Failed to create oFono modem for %s", path);
+ }
+}
+
+static void
+ofono_signal_cb (GDBusProxy *proxy,
+ gchar *sender_name,
+ gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ NMModemManager *self = NM_MODEM_MANAGER (user_data);
+ gchar *object_path;
+ NMModem *modem;
+
+ if (g_strcmp0 (signal_name, "ModemAdded") == 0) {
+ g_variant_get (parameters, "(oa{sv})", &object_path, NULL);
+ nm_log_info (LOGD_MB, "oFono modem appeared: %s", object_path);
+
+ ofono_create_modem (NM_MODEM_MANAGER (user_data), object_path);
+ g_free (object_path);
+ } else if (g_strcmp0 (signal_name, "ModemRemoved") == 0) {
+ g_variant_get (parameters, "(o)", &object_path);
+ nm_log_info (LOGD_MB, "oFono modem removed: %s", object_path);
+
+ modem = (NMModem *) g_hash_table_lookup (self->priv->modems, object_path);
+ if (modem) {
+ nm_modem_emit_removed (modem);
+ g_hash_table_remove (self->priv->modems, object_path);
+ } else {
+ nm_log_warn (LOGD_MB, "could not remove modem %s, not found in table",
+ object_path);
+ }
+ g_free (object_path);
+ }
+}
+
+static void
+ofono_enumerate_devices_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
+{
+ NMModemManager *manager = NM_MODEM_MANAGER (user_data);
+ gs_free_error GError *error = NULL;
+ GVariant *results;
+ GVariantIter *iter;
+ const char *path;
+
+ results = g_dbus_proxy_call_finish (proxy, res, &error);
+ if (results) {
+ g_variant_get (results, "(a(oa{sv}))", &iter);
+ while (g_variant_iter_loop (iter, "(&oa{sv})", &path, NULL))
+ ofono_create_modem (manager, path);
+ g_variant_iter_free (iter);
+ g_variant_unref (results);
+ }
+
+ if (error) {
+ nm_log_warn (LOGD_MB, "failed to enumerate oFono devices: %s",
+ error->message);
+ }
+}
+
+static void
+ofono_check_name_owner (NMModemManager *self)
+{
+ gs_free char *name_owner = NULL;
+
+ name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (self->priv->ofono_proxy));
+ if (name_owner) {
+ nm_log_info (LOGD_MB, "oFono is now available");
+
+ g_dbus_proxy_call (self->priv->ofono_proxy,
+ "GetModems",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) ofono_enumerate_devices_done,
+ g_object_ref (self));
+ } else {
+ GHashTableIter iter;
+ NMModem *modem;
+
+ nm_log_info (LOGD_MB, "oFono disappeared from bus");
+
+ /* Remove any oFono modems that might be left around */
+ g_hash_table_iter_init (&iter, self->priv->modems);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer) &modem)) {
+ if (NM_IS_MODEM_OFONO (modem)) {
+ nm_modem_emit_removed (modem);
+ g_hash_table_iter_remove (&iter);
+ }
+ }
+ }
+}
+
+static void
+ofono_name_owner_changed (GDBusProxy *ofono_proxy,
+ GParamSpec *pspec,
+ NMModemManager *self)
+{
+ ofono_check_name_owner (self);
+}
+
+static void
+ofono_proxy_new_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
+ gs_free_error GError *error = NULL;
+
+ self->priv->ofono_proxy = g_dbus_proxy_new_finish (res, &error);
+ if (error) {
+ nm_log_warn (LOGD_MB, "error getting oFono bus proxy: %s", error->message);
+ return;
+ }
+
+ g_signal_connect (self->priv->ofono_proxy,
+ "notify::g-name-owner",
+ G_CALLBACK (ofono_name_owner_changed),
+ self);
+
+ g_signal_connect (self->priv->ofono_proxy,
+ "g-signal",
+ G_CALLBACK (ofono_signal_cb),
+ self);
+
+ ofono_check_name_owner (self);
+}
+
+static void
+ensure_ofono_client (NMModemManager *self)
+{
+ g_assert (self->priv->dbus_connection);
+ g_dbus_proxy_new (self->priv->dbus_connection,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ OFONO_DBUS_SERVICE,
+ OFONO_DBUS_PATH,
+ OFONO_DBUS_INTERFACE,
+ NULL,
+ (GAsyncReadyCallback) ofono_proxy_new_cb,
+ g_object_ref (self));
+}
+#endif
+
static void
modem_manager_poke_cb (GDBusConnection *connection,
GAsyncResult *res,
@@ -238,19 +385,18 @@ modem_manager_poke_cb (GDBusConnection *connection,
result = g_dbus_connection_call_finish (connection, res, &error);
if (error) {
- /* Ignore common errors when MM is not installed and such */
- if ( !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
- && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_EXEC_FAILED)
- && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FORK_FAILED)
- && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FAILED)
- && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMEOUT)
- && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
- nm_log_dbg (LOGD_MB, "error poking ModemManager: %s", error->message);
+ nm_log_warn (LOGD_MB, "error poking ModemManager: %s",
+ error ? error->message : "");
+
+ /* Don't reschedule poke is MM service doesn't exist. */
+ if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
+ && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
+
+ /* Setup timeout to relaunch */
+ schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
}
- g_error_free (error);
- /* Setup timeout to relaunch */
- schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
+ g_error_free (error);
} else
g_variant_unref (result);
@@ -279,13 +425,12 @@ modem_manager_poke (NMModemManager *self)
static void
modem_manager_check_name_owner (NMModemManager *self)
{
- gchar *name_owner;
+ gs_free gchar *name_owner = NULL;
name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (self->priv->modem_manager));
if (name_owner) {
/* Available! */
modem_manager_available (self);
- g_free (name_owner);
return;
}
@@ -303,7 +448,8 @@ manager_new_ready (GObject *source,
GError *error = NULL;
- g_assert (!self->priv->modem_manager);
+ g_return_if_fail (!self->priv->modem_manager);
+
self->priv->modem_manager = mm_manager_new_finish (res, &error);
if (!self->priv->modem_manager) {
/* We're not really supposed to get any error here. If we do get one,
@@ -316,20 +462,20 @@ manager_new_ready (GObject *source,
} else {
/* Setup signals in the GDBusObjectManagerClient */
self->priv->mm_name_owner_changed_id =
- g_signal_connect (self->priv->modem_manager,
- "notify::name-owner",
- G_CALLBACK (modem_manager_name_owner_changed),
- self);
+ g_signal_connect (self->priv->modem_manager,
+ "notify::name-owner",
+ G_CALLBACK (modem_manager_name_owner_changed),
+ self);
self->priv->mm_object_added_id =
- g_signal_connect (self->priv->modem_manager,
- "object-added",
- G_CALLBACK (modem_object_added),
- self);
+ g_signal_connect (self->priv->modem_manager,
+ "object-added",
+ G_CALLBACK (modem_object_added),
+ self);
self->priv->mm_object_removed_id =
- g_signal_connect (self->priv->modem_manager,
- "object-removed",
- G_CALLBACK (modem_object_removed),
- self);
+ g_signal_connect (self->priv->modem_manager,
+ "object-removed",
+ G_CALLBACK (modem_object_removed),
+ self);
modem_manager_check_name_owner (self);
}
@@ -339,7 +485,7 @@ manager_new_ready (GObject *source,
}
static void
-ensure_client (NMModemManager *self)
+ensure_modem_manager (NMModemManager *self)
{
g_assert (self->priv->dbus_connection);
@@ -359,46 +505,12 @@ ensure_client (NMModemManager *self)
modem_manager_check_name_owner (self);
}
-static void
-bus_get_ready (GObject *source,
- GAsyncResult *res,
- NMModemManager *self)
-{
- /* Note we always get an extra reference to self here */
-
- GError *error = NULL;
-
- self->priv->dbus_connection = g_bus_get_finish (res, &error);
- if (!self->priv->dbus_connection) {
- nm_log_warn (LOGD_CORE, "error getting bus connection: %s", error->message);
- g_error_free (error);
- /* Setup timeout to relaunch */
- schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
- } else {
- /* Got the bus, ensure client */
- ensure_client (self);
- }
-
- /* Balance refcount */
- g_object_unref (self);
-}
-
static gboolean
-ensure_bus (NMModemManager *self)
+mm_launch_cb (NMModemManager *self)
{
- /* Clear launch ID */
self->priv->mm_launch_id = 0;
-
- if (!self->priv->dbus_connection)
- g_bus_get (G_BUS_TYPE_SYSTEM,
- NULL,
- (GAsyncReadyCallback)bus_get_ready,
- g_object_ref (self));
- else
- /* If bus is already available, ensure client */
- ensure_client (self);
-
- return FALSE;
+ ensure_modem_manager (self);
+ return G_SOURCE_REMOVE;
}
static void
@@ -407,11 +519,31 @@ schedule_modem_manager_relaunch (NMModemManager *self,
{
/* No need to pass an extra reference to self; timeout/idle will be
* cancelled if the object gets disposed. */
-
if (n_seconds)
- self->priv->mm_launch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)ensure_bus, self);
+ self->priv->mm_launch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)mm_launch_cb, self);
else
- self->priv->mm_launch_id = g_idle_add ((GSourceFunc)ensure_bus, self);
+ self->priv->mm_launch_id = g_idle_add ((GSourceFunc)mm_launch_cb, self);
+}
+
+static void
+bus_get_ready (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
+ gs_free_error GError *error = NULL;
+
+ self->priv->dbus_connection = g_bus_get_finish (res, &error);
+ if (!self->priv->dbus_connection) {
+ nm_log_warn (LOGD_MB, "error getting bus connection: %s", error->message);
+ return;
+ }
+
+ /* Got the bus, ensure clients */
+ ensure_modem_manager (self);
+#if WITH_OFONO
+ ensure_ofono_client (self);
+#endif
}
/************************************************************************/
@@ -423,7 +555,11 @@ nm_modem_manager_init (NMModemManager *self)
self->priv->modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
- schedule_modem_manager_relaunch (self, 0);
+ // FIXME: this doesn't handle bus-daemon restart
+ g_bus_get (G_BUS_TYPE_SYSTEM,
+ NULL,
+ (GAsyncReadyCallback)bus_get_ready,
+ g_object_ref (self));
}
static void
@@ -433,8 +569,16 @@ dispose (GObject *object)
nm_clear_g_source (&self->priv->mm_launch_id);
- modem_manager_clear_signals (self);
- g_clear_object (&self->priv->modem_manager);
+ clear_modem_manager (self);
+
+#if WITH_OFONO
+ if (self->priv->ofono_proxy) {
+ g_signal_handlers_disconnect_by_func (self->priv->ofono_proxy, ofono_name_owner_changed, self);
+ g_signal_handlers_disconnect_by_func (self->priv->ofono_proxy, ofono_signal_cb, self);
+ g_clear_object (&self->priv->ofono_proxy);
+ }
+#endif
+
g_clear_object (&self->priv->dbus_connection);
if (self->priv->modems) {
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
new file mode 100644
index 0000000000..86492d3d98
--- /dev/null
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -0,0 +1,1202 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2013 - 2016 Canonical Ltd.
+ */
+
+#include "nm-default.h"
+
+#include "nm-modem-ofono.h"
+
+#include <string.h>
+
+#include "nm-core-internal.h"
+#include "nm-device-private.h"
+#include "nm-modem.h"
+#include "nm-platform.h"
+
+G_DEFINE_TYPE (NMModemOfono, nm_modem_ofono, NM_TYPE_MODEM)
+
+#define NM_MODEM_OFONO_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_MODEM_OFONO, NMModemOfonoPrivate))
+
+#define VARIANT_IS_OF_TYPE_BOOLEAN(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_BOOLEAN) ))
+#define VARIANT_IS_OF_TYPE_STRING(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING) ))
+#define VARIANT_IS_OF_TYPE_OBJECT_PATH(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_OBJECT_PATH) ))
+#define VARIANT_IS_OF_TYPE_STRING_ARRAY(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING_ARRAY) ))
+#define VARIANT_IS_OF_TYPE_DICTIONARY(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_DICTIONARY) ))
+
+typedef struct {
+ GHashTable *connect_properties;
+
+ GDBusProxy *modem_proxy;
+ GDBusProxy *connman_proxy;
+ GDBusProxy *context_proxy;
+ GDBusProxy *sim_proxy;
+
+ GError *property_error;
+
+ char *context_path;
+ char *imsi;
+
+ gboolean modem_online;
+ gboolean gprs_attached;
+
+ NMIP4Config *ip4_config;
+} NMModemOfonoPrivate;
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN LOGD_MB
+#define _NMLOG_PREFIX_NAME "modem-ofono"
+#define _NMLOG(level, ...) \
+ G_STMT_START { \
+ const NMLogLevel _level = (level); \
+ \
+ if (nm_logging_enabled (_level, (_NMLOG_DOMAIN))) { \
+ NMModemOfono *const __self = (self); \
+ char __prefix_name[128]; \
+ const char *__uid; \
+ \
+ _nm_log (_level, (_NMLOG_DOMAIN), 0, \
+ "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+ _NMLOG_PREFIX_NAME, \
+ (__self \
+ ? ({ \
+ ((__uid = nm_modem_get_uid ((NMModem *) __self)) \
+ ? nm_sprintf_buf (__prefix_name, "[%s]", __uid) \
+ : "(null)"); \
+ }) \
+ : "") \
+ _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+ } \
+ } G_STMT_END
+
+/*****************************************************************************/
+
+static gboolean
+ip_string_to_network_address (const gchar *str,
+ guint32 *out)
+{
+ guint32 addr = 0;
+ gboolean success = FALSE;
+
+ if (!str || inet_pton (AF_INET, str, &addr) != 1)
+ addr = 0;
+ else
+ success = TRUE;
+
+ *out = (guint32)addr;
+ return success;
+}
+
+static void
+get_capabilities (NMModem *_self,
+ NMDeviceModemCapabilities *modem_caps,
+ NMDeviceModemCapabilities *current_caps)
+{
+ /* FIXME: auto-detect capabilities to allow LTE */
+ *modem_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
+ *current_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
+}
+
+static void
+update_modem_state (NMModemOfono *self)
+{
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+ NMModemState state = nm_modem_get_state (NM_MODEM (self));
+ NMModemState new_state = NM_MODEM_STATE_DISABLED;
+ const char *reason = NULL;
+
+ _LOGI ("'Attached': %s 'Online': %s 'IMSI': %s",
+ priv->gprs_attached ? "true" : "false",
+ priv->modem_online ? "true" : "false",
+ priv->imsi);
+
+ if (priv->modem_online == FALSE) {
+ reason = "modem 'Online=false'";
+ } else if (priv->imsi == NULL && state != NM_MODEM_STATE_ENABLING) {
+ reason = "modem not ready";
+ } else if (priv->gprs_attached == FALSE) {
+ new_state = NM_MODEM_STATE_SEARCHING;
+ reason = "modem searching";
+ } else {
+ new_state = NM_MODEM_STATE_REGISTERED;
+ reason = "modem ready";
+ }
+
+ if (state != new_state)
+ nm_modem_set_state (NM_MODEM (self), new_state, reason);
+}
+
+/* Disconnect */
+typedef struct {
+ NMModemOfono *self;
+ GSimpleAsyncResult *result;
+ GCancellable *cancellable;
+ gboolean warn;
+} DisconnectContext;
+
+static void
+disconnect_context_complete (DisconnectContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ if (ctx->cancellable)
+ g_object_unref (ctx->cancellable);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_slice_free (DisconnectContext, ctx);
+}
+
+static gboolean
+disconnect_context_complete_if_cancelled (DisconnectContext *ctx)
+{
+ GError *error = NULL;
+
+ if (g_cancellable_set_error_if_cancelled (ctx->cancellable, &error)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ disconnect_context_complete (ctx);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+disconnect_finish (NMModem *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+static void
+disconnect_done (GDBusProxy *proxy,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ DisconnectContext *ctx = (DisconnectContext*) user_data;
+ NMModemOfono *self = ctx->self;
+ GError *error = NULL;
+
+ g_dbus_proxy_call_finish (proxy, result, &error);
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ _LOGD ("disconnect cancelled");
+ return;
+ }
+
+ if (error) {
+ if (ctx->warn)
+ _LOGW ("failed to disconnect modem: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ _LOGD ("modem disconnected");
+
+ update_modem_state (self);
+ disconnect_context_complete (ctx);
+}
+
+static void
+disconnect (NMModem *modem,
+ gboolean warn,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ NMModemOfono *self = NM_MODEM_OFONO (modem);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+ DisconnectContext *ctx;
+ NMModemState state = nm_modem_get_state (NM_MODEM (self));
+
+ _LOGD ("warn: %s modem_state: %s",
+ warn ? "TRUE" : "FALSE",
+ nm_modem_state_to_string (state));
+
+ if (state != NM_MODEM_STATE_CONNECTED)
+ return;
+
+ ctx = g_slice_new (DisconnectContext);
+ ctx->self = g_object_ref (self);
+ ctx->warn = warn;
+
+ if (callback) {
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ disconnect);
+ }
+ /* Setup cancellable */
+ ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+ if (disconnect_context_complete_if_cancelled (ctx))
+ return;
+
+ nm_modem_set_state (NM_MODEM (self),
+ NM_MODEM_STATE_DISCONNECTING,
+ nm_modem_state_to_string (NM_MODEM_STATE_DISCONNECTING));
+
+ g_dbus_proxy_call (priv->context_proxy,
+ "SetProperty",
+ g_variant_new ("(sv)",
+ "Active",
+ g_variant_new ("b", warn)),
+ G_DBUS_CALL_FLAGS_NONE,
+ 20000,
+ NULL,
+ (GAsyncReadyCallback) disconnect_done,
+ ctx);
+}
+
+static void
+deactivate_cleanup (NMModem *_self, NMDevice *device)
+{
+ NMModemOfono *self = NM_MODEM_OFONO (_self);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+ /* TODO: cancel SimpleConnect() if any */
+
+ g_clear_object (&priv->ip4_config);
+
+ /* Chain up parent's */
+ NM_MODEM_CLASS (nm_modem_ofono_parent_class)->deactivate_cleanup (_self, device);
+}
+
+
+static gboolean
+check_connection_compatible (NMModem *modem,
+ NMConnection *connection)
+{
+ NMModemOfono *self = NM_MODEM_OFONO (modem);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+ NMSettingConnection *s_con;
+ NMSettingGsm *s_gsm;
+ const char *uuid;
+ const char *id;
+
+ s_con = nm_connection_get_setting_connection (connection);
+ g_assert (s_con);
+
+ uuid = nm_connection_get_uuid (connection);
+ id = nm_connection_get_id (connection);
+
+ s_gsm = nm_connection_get_setting_gsm (connection);
+ if (!s_gsm)
+ return FALSE;
+
+ if (!priv->imsi) {
+ _LOGW ("skipping %s/%s: no IMSI", uuid, id);
+ return FALSE;
+ }
+
+ if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_GSM_SETTING_NAME)) {
+ _LOGD ("skipping %s/%s: not GSM", uuid, id);
+ return FALSE;
+ }
+
+ if (!g_strrstr (id, "/context")) {
+ _LOGD ("skipping %s/%s: unexpected ID", uuid, id);
+ return FALSE;
+ }
+
+ if (!g_strrstr (id, priv->imsi)) {
+ _LOGD ("skipping %s/%s: ID doesn't contain IMSI", uuid, id);
+ return FALSE;
+ }
+
+ _LOGD ("%s/%s compatible with IMSI %s", uuid, id, priv->imsi);
+ return TRUE;
+}
+
+static void
+handle_sim_property (GDBusProxy *proxy,
+ const char *property,
+ GVariant *v,
+ gpointer user_data)
+{
+ NMModemOfono *self = NM_MODEM_OFONO (user_data);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+ if (g_strcmp0 (property, "SubscriberIdentity") == 0 && VARIANT_IS_OF_TYPE_STRING (v)) {
+ gsize length;
+ const char *value_str = g_variant_get_string (v, &length);
+
+ _LOGD ("SubscriberIdentify found");
+
+ /* Check for empty DBus string value */
+ if (length &&
+ g_strcmp0 (value_str, "(null)") != 0 &&
+ g_strcmp0 (value_str, priv->imsi) != 0) {
+
+ if (priv->imsi != NULL) {
+ _LOGW ("SimManager:'SubscriberIdentity' changed: %s", priv->imsi);
+ g_free(priv->imsi);
+ }
+
+ priv->imsi = g_strdup (value_str);
+ update_modem_state (self);
+ }
+ }
+}
+
+static void
+sim_property_changed (GDBusProxy *proxy,
+ const char *property,
+ GVariant *v,
+ gpointer user_data)
+{
+ GVariant *v_child = g_variant_get_child_value (v, 0);
+
+ handle_sim_property (proxy, property, v_child, user_data);
+ g_variant_unref (v_child);
+}
+
+static void
+sim_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+ gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+ GError *error = NULL;
+ GVariant *v_properties, *v_dict, *v;
+ GVariantIter i;
+ const char *property;
+
+ v_properties = _nm_dbus_proxy_call_finish (proxy,
+ result,
+ G_VARIANT_TYPE ("(a{sv})"),
+ &error);
+ if (!v_properties) {
+ g_dbus_error_strip_remote_error (error);
+ _LOGW ("error getting sim properties: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ _LOGD ("sim v_properties is type: %s", g_variant_get_type_string (v_properties));
+
+ v_dict = g_variant_get_child_value (v_properties, 0);
+ if (!v_dict) {
+ _LOGW ("error getting sim properties: no v_dict");
+ return;
+ }
+
+ _LOGD ("sim v_dict is type: %s", g_variant_get_type_string (v_dict));
+
+ /*
+ * TODO:
+ * 1) optimize by looking up properties ( Online, Interfaces ), instead
+ * of iterating
+ *
+ * 2) reduce code duplication between all of the get_properties_done
+ * functions in this class.
+ */
+
+ g_variant_iter_init (&i, v_dict);
+ while (g_variant_iter_next (&i, "{&sv}", &property, &v)) {
+ handle_sim_property (NULL, property, v, self);
+ g_variant_unref (v);
+ }
+
+ g_variant_unref (v_dict);
+ g_variant_unref (v_properties);
+}
+
+static void
+handle_sim_iface (NMModemOfono *self, gboolean found)
+{
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+ _LOGD ("SimManager interface %sfound", found ? "" : "not ");
+
+ if (!found && priv->sim_proxy) {
+ _LOGI ("SimManager interface disappeared");
+ g_signal_handlers_disconnect_by_data (priv->sim_proxy, NM_MODEM_OFONO (self));
+ g_clear_object (&priv->sim_proxy);
+ g_clear_pointer (&priv->imsi, g_free);
+ update_modem_state (self);
+ } else if (found && !priv->sim_proxy) {
+ GError *error = NULL;
+
+ _LOGI ("found new SimManager interface");
+
+ priv->sim_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+ | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL, /* GDBusInterfaceInfo */
+ OFONO_DBUS_SERVICE,
+ nm_modem_get_path (NM_MODEM (self)),
+ OFONO_DBUS_INTERFACE_SIM_MANAGER,
+ NULL, /* GCancellable */
+ &error);
+ if (priv->sim_proxy == NULL) {
+ _LOGW ("failed to create SimManager proxy: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* Watch for custom ofono PropertyChanged signals */
+ _nm_dbus_signal_connect (priv->sim_proxy,
+ "PropertyChanged",
+ G_VARIANT_TYPE ("(sv)"),
+ G_CALLBACK (sim_property_changed),
+ self);
+
+ g_dbus_proxy_call (priv->sim_proxy,
+ "GetProperties",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ 20000,
+ NULL,
+ (GAsyncReadyCallback) sim_get_properties_done,
+ g_object_ref (self));
+ }
+}
+
+static void
+handle_connman_property (GDBusProxy *proxy,
+ const char *property,
+ GVariant *v,
+ gpointer user_data)
+{
+ NMModemOfono *self = NM_MODEM_OFONO (user_data);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+ if (g_strcmp0 (property, "Attached") == 0 && VARIANT_IS_OF_TYPE_BOOLEAN (v)) {
+ gboolean attached = g_variant_get_boolean (v);
+ gboolean old_attached = priv->gprs_attached;
+
+ _LOGD ("Attached: %s", attached ? "True" : "False");
+
+ if (priv->gprs_attached != attached) {
+ priv->gprs_attached = attached;
+
+ _LOGI ("Attached %s -> %s",
+ old_attached ? "true" : "false",
+ attached ? "true" : "false");
+
+ update_modem_state (self);
+ }
+ }
+}
+
+static void
+connman_property_changed (GDBusProxy *proxy,
+ const char *property,
+ GVariant *v,
+ gpointer user_data)
+{
+ GVariant *v_child = g_variant_get_child_value (v, 0);
+
+ handle_connman_property (proxy, property, v_child, user_data);
+ g_variant_unref (v_child);
+}
+
+static void
+connman_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+ gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+ GError *error = NULL;
+ GVariant *v_properties, *v_dict, *v;
+ GVariantIter i;
+ const char *property;
+
+ v_properties = _nm_dbus_proxy_call_finish (proxy,
+ result,
+ G_VARIANT_TYPE ("(a{sv})"),
+ &error);
+ if (!v_properties) {
+ g_dbus_error_strip_remote_error (error);
+ _LOGW ("error getting connman properties: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ v_dict = g_variant_get_child_value (v_properties, 0);
+
+ /*
+ * TODO:
+ * 1) optimize by looking up properties ( Online, Interfaces ), instead
+ * of iterating
+ *
+ * 2) reduce code duplication between all of the get_properties_done
+ * functions in this class.
+ */
+
+ g_variant_iter_init (&i, v_dict);
+ while (g_variant_iter_next (&i, "{&sv}", &property, &v)) {
+ handle_connman_property (NULL, property, v, self);
+ g_variant_unref (v);
+ }
+
+ g_variant_unref (v_dict);
+ g_variant_unref (v_properties);
+}
+
+static void
+handle_connman_iface (NMModemOfono *self, gboolean found)
+{
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+ _LOGD ("ConnectionManager interface %sfound", found ? "" : "not ");
+
+ if (!found && priv->connman_proxy) {
+ _LOGI ("ConnectionManager interface disappeared");
+
+ g_signal_handlers_disconnect_by_data (priv->connman_proxy, NM_MODEM_OFONO (self));
+ g_clear_object (&priv->connman_proxy);
+
+ /* The connection manager proxy disappeared, we should
+ * consider the modem disabled.
+ */
+ priv->gprs_attached = FALSE;
+
+ update_modem_state (self);
+ } else if (found && !priv->connman_proxy) {
+ GError *error = NULL;
+
+ _LOGI ("found new ConnectionManager interface");
+
+ priv->connman_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+ | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL, /* GDBusInterfaceInfo */
+ OFONO_DBUS_SERVICE,
+ nm_modem_get_path (NM_MODEM (self)),
+ OFONO_DBUS_INTERFACE_CONNECTION_MANAGER,
+ NULL, /* GCancellable */
+ &error);
+ if (priv->connman_proxy == NULL) {
+ _LOGW ("failed to create ConnectionManager proxy: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* Watch for custom ofono PropertyChanged signals */
+ _nm_dbus_signal_connect (priv->connman_proxy,
+ "PropertyChanged",
+ G_VARIANT_TYPE ("(sv)"),
+ G_CALLBACK (connman_property_changed),
+ self);
+
+ g_dbus_proxy_call (priv->connman_proxy,
+ "GetProperties",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ 20000,
+ NULL,
+ (GAsyncReadyCallback) connman_get_properties_done,
+ g_object_ref (self));
+ }
+}
+
+static void
+handle_modem_property (GDBusProxy *proxy,
+ const char *property,
+ GVariant *v,
+ gpointer user_data)
+{
+ NMModemOfono *self = NM_MODEM_OFONO (user_data);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+ if ((g_strcmp0 (property, "Online") == 0) && VARIANT_IS_OF_TYPE_BOOLEAN (v)) {
+ gboolean online = g_variant_get_boolean (v);
+
+ _LOGD ("Online: %s", online ? "True" : "False");
+
+ if (online != priv->modem_online) {
+ priv->modem_online = online;
+ _LOGI ("modem is now %s", online ? "Online" : "Offline");
+ update_modem_state (self);
+ }
+
+ } else if ((g_strcmp0 (property, "Interfaces") == 0) && VARIANT_IS_OF_TYPE_STRING_ARRAY (v)) {
+ const char **array, **iter;
+ gboolean found_connman = FALSE;
+ gboolean found_sim = FALSE;
+
+ _LOGD ("Interfaces found");
+
+ array = g_variant_get_strv (v, NULL);
+ if (array) {
+ for (iter = array; *iter; iter++) {
+ if (g_strcmp0 (OFONO_DBUS_INTERFACE_SIM_MANAGER, *iter) == 0)
+ found_sim = TRUE;
+ else if (g_strcmp0 (OFONO_DBUS_INTERFACE_CONNECTION_MANAGER, *iter) == 0)
+ found_connman = TRUE;
+ }
+ g_free (array);
+ }
+
+ handle_sim_iface (self, found_sim);
+ handle_connman_iface (self, found_connman);
+ }
+}
+
+static void
+modem_property_changed (GDBusProxy *proxy,
+ const char *property,
+ GVariant *v,
+ gpointer user_data)
+{
+ GVariant *v_child = g_variant_get_child_value (v, 0);
+
+ handle_modem_property (proxy, property, v_child, user_data);
+ g_variant_unref (v_child);
+}
+
+static void
+modem_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+ gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+ GError *error = NULL;
+ GVariant *v_properties, *v_dict, *v;
+ GVariantIter i;
+ const char *property;
+
+ v_properties = _nm_dbus_proxy_call_finish (proxy,
+ result,
+ G_VARIANT_TYPE ("(a{sv})"),
+ &error);
+ if (!v_properties) {
+ g_dbus_error_strip_remote_error (error);
+ _LOGW ("error getting modem properties: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ v_dict = g_variant_get_child_value (v_properties, 0);
+ if (!v_dict) {
+ _LOGW ("error getting modem properties: no v_dict");
+ return;
+ }
+
+ /*
+ * TODO:
+ * 1) optimize by looking up properties ( Online, Interfaces ), instead
+ * of iterating
+ *
+ * 2) reduce code duplication between all of the get_properties_done
+ * functions in this class.
+ */
+
+ g_variant_iter_init (&i, v_dict);
+ while (g_variant_iter_next (&i, "{&sv}", &property, &v)) {
+ handle_modem_property (NULL, property, v, self);
+ g_variant_unref (v);
+ }
+
+ g_variant_unref (v_dict);
+ g_variant_unref (v_properties);
+}
+
+NMModem *
+nm_modem_ofono_new (const char *path)
+{
+ gs_free char *basename = NULL;
+
+ g_return_val_if_fail (path != NULL, NULL);
+
+ nm_log_info (LOGD_MB, "ofono: creating new Ofono modem path %s", path);
+
+ /* Use short modem name (not its object path) as the NM device name (which
+ * comes from NM_MODEM_UID)and the device ID.
+ */
+ basename = g_path_get_basename (path);
+
+ return (NMModem *) g_object_new (NM_TYPE_MODEM_OFONO,
+ NM_MODEM_PATH, path,
+ NM_MODEM_UID, basename,
+ NM_MODEM_DEVICE_ID, basename,
+ NM_MODEM_CONTROL_PORT, "ofono", /* mandatory */
+ NM_MODEM_DRIVER, "ofono",
+ NM_MODEM_STATE, NM_MODEM_STATE_INITIALIZING,
+ NULL);
+}
+
+static void
+stage1_prepare_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+ gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ g_clear_pointer (&priv->connect_properties, g_hash_table_destroy);
+
+ g_dbus_proxy_call_finish (proxy, result, &error);
+ if (error) {
+ _LOGW ("connection failed: %s", error->message);
+
+ g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE,
+ NM_DEVICE_STATE_REASON_MODEM_BUSY);
+ /*
+ * FIXME: add code to check for InProgress so that the
+ * connection doesn't continue to try and activate,
+ * leading to the connection being disabled, and a 5m
+ * timeout...
+ */
+
+ g_clear_error (&error);
+ }
+}
+
+static void
+context_property_changed (GDBusProxy *proxy,
+ const char *property,
+ GVariant *v,
+ gpointer user_data)
+{
+ NMModemOfono *self = NM_MODEM_OFONO (user_data);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+ NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+ NMPlatformIP4Address addr;
+ gboolean ret = FALSE;
+ GVariant *v_dict;
+ const gchar *s, *addr_s;
+ const gchar **array, **iter;
+ guint32 address_network, gateway_network;
+ guint prefix = 0;
+
+ _LOGD ("PropertyChanged: %s", property);
+
+ /*
+ * TODO: might be a good idea and re-factor this to mimic bluez-device,
+ * ie. have this function just check the key, and call a sub-func to
+ * handle the action.
+ */
+
+ if (g_strcmp0 (property, "Settings") != 0)
+ return;
+
+ v_dict = g_variant_get_child_value (v, 0);
+ if (!v_dict) {
+ _LOGW ("error getting IPv4 Settings: no v_dict");
+ goto out;
+ }
+
+ _LOGI ("IPv4 static Settings:");
+
+ if (g_variant_lookup (v_dict, "Interface", "&s", &s)) {
+ if (s && strlen (s)) {
+ _LOGD ("Interface: %s", s);
+ g_object_set (self,
+ NM_MODEM_DATA_PORT, g_strdup (s),
+ NM_MODEM_IP4_METHOD, NM_MODEM_IP_METHOD_STATIC,
+ NULL);
+ } else {
+ _LOGW ("Settings 'Interface'; empty");
+ goto out;
+ }
+
+ } else {
+ _LOGW ("Settings 'Interface' missing");
+ goto out;
+ }
+
+ /* TODO: verify handling of ip4_config; check other places it's used... */
+ g_clear_object (&priv->ip4_config);
+
+ memset (&addr, 0, sizeof (addr));
+
+ /*
+ * TODO:
+ *
+ * NM 1.2 changed the NMIP4Config constructor to take an ifindex
+ * ( vs. void pre 1.2 ), to tie config instance to a specific
+ * platform interface.
+ *
+ * This doesn't work for ofono, as the devices are created
+ * dynamically ( eg. ril_0, ril_1 ) in NMModemManager. The
+ * device created doesn't really map directly to a platform
+ * link. The closest would be one of the devices owned by
+ * rild ( eg. ccmin0 ), which is passed to us above as
+ * 'Interface'.
+ *
+ * This needs discussion with upstream.
+ */
+ priv->ip4_config = nm_ip4_config_new (0);
+
+ /* TODO: simply if/else error logic! */
+
+ if (g_variant_lookup (v_dict, "Address", "&s", &addr_s)) {
+ _LOGD ("Address: %s", addr_s);
+
+ if (ip_string_to_network_address (addr_s, &address_network)) {
+ addr.address = address_network;
+ addr.addr_source = NM_IP_CONFIG_SOURCE_WWAN;
+ } else {
+ _LOGW ("can't convert 'Address' %s to addr", s);
+ goto out;
+ }
+
+ } else {
+ _LOGW ("Settings 'Address' missing");
+ goto out;
+ }
+
+ if (g_variant_lookup (v_dict, "Netmask", "&s", &s)) {
+ _LOGD ("Netmask: %s", s);
+
+ if (s && ip_string_to_network_address (s, &address_network)) {
+ prefix = nm_utils_ip4_netmask_to_prefix (address_network);
+ if (prefix > 0)
+ addr.plen = prefix;
+ } else {
+ _LOGW ("invalid 'Netmask': %s", s);
+ goto out;
+ }
+ } else {
+ _LOGW ("Settings 'Netmask' missing");
+ goto out;
+ }
+
+ _LOGI ("Address: %s/%d", addr_s, prefix);
+
+ nm_ip4_config_add_address (priv->ip4_config, &addr);
+
+ if (g_variant_lookup (v_dict, "Gateway", "&s", &s)) {
+ if (s && ip_string_to_network_address (s, &gateway_network)) {
+ _LOGI ("Gateway: %s", s);
+ nm_ip4_config_set_gateway (priv->ip4_config, gateway_network);
+ } else {
+ _LOGW ("invalid 'Gateway': %s", s);
+ goto out;
+ }
+ nm_ip4_config_set_gateway (priv->ip4_config, gateway_network);
+ } else {
+ _LOGW ("Settings 'Gateway' missing");
+ goto out;
+ }
+
+ if (g_variant_lookup (v_dict, "DomainNameServers", "^a&s", &array)) {
+ if (array) {
+ for (iter = array; *iter; iter++) {
+ if (ip_string_to_network_address (*iter, &address_network) && address_network > 0) {
+ _LOGI ("DNS: %s", *iter);
+ nm_ip4_config_add_nameserver (priv->ip4_config, address_network);
+ } else {
+ _LOGW ("invalid NameServer: %s", *iter);
+ }
+ }
+
+ if (iter == array) {
+ _LOGW ("Settings: 'DomainNameServers': none specified");
+ g_free (array);
+ goto out;
+ }
+ g_free (array);
+ }
+ } else {
+ _LOGW ("Settings 'DomainNameServers' missing");
+ goto out;
+ }
+
+ if (g_variant_lookup (v_dict, "MessageProxy", "&s", &s)) {
+ _LOGI ("MessageProxy: %s", s);
+ if (s && ip_string_to_network_address (s, &address_network)) {
+ NMPlatformIP4Route mms_route;
+
+ mms_route.network = address_network;
+ mms_route.plen = 32;
+ mms_route.gateway = gateway_network;
+
+ mms_route.metric = 1;
+
+ nm_ip4_config_add_route (priv->ip4_config, &mms_route);
+ } else {
+ _LOGW ("invalid MessageProxy: %s", s);
+ }
+ }
+
+ ret = TRUE;
+
+out:
+ if (nm_modem_get_state (NM_MODEM (self)) != NM_MODEM_STATE_CONNECTED) {
+ _LOGI ("emitting PREPARE_RESULT: %s", ret ? "TRUE" : "FALSE");
+ if (!ret)
+ reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
+ g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, ret, reason);
+ } else {
+ _LOGW ("MODEM_PPP_FAILED");
+ g_signal_emit_by_name (self, NM_MODEM_PPP_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED);
+ }
+}
+
+static NMActStageReturn
+static_stage3_ip4_config_start (NMModem *_self,
+ NMActRequest *req,
+ NMDeviceStateReason *reason)
+{
+ NMModemOfono *self = NM_MODEM_OFONO (_self);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+ NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+ GError *error = NULL;
+
+ if (priv->ip4_config) {
+ _LOGD ("IP4 config is done; setting modem_state -> CONNECTED");
+ g_signal_emit_by_name (self, NM_MODEM_IP4_CONFIG_RESULT, priv->ip4_config, error);
+
+ /* Signal listener takes ownership of the IP4Config */
+ priv->ip4_config = NULL;
+
+ nm_modem_set_state (NM_MODEM (self),
+ NM_MODEM_STATE_CONNECTED,
+ nm_modem_state_to_string (NM_MODEM_STATE_CONNECTED));
+ ret = NM_ACT_STAGE_RETURN_POSTPONE;
+ }
+
+ return ret;
+}
+
+static void
+context_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+ gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ priv->context_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+ if (error) {
+ _LOGE ("failed to create ofono ConnectionContext DBus proxy: %s", error->message);
+ g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE,
+ NM_DEVICE_STATE_REASON_MODEM_BUSY);
+ return;
+ }
+
+ if (!priv->gprs_attached) {
+ g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE,
+ NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
+ return;
+ }
+
+ /* We have an old copy of the settings from a previous activation,
+ * clear it so that we can gate getting the IP config from oFono
+ * on whether or not we have already received them
+ */
+ g_clear_object (&priv->ip4_config);
+
+ /* Watch for custom ofono PropertyChanged signals */
+ _nm_dbus_signal_connect (priv->context_proxy,
+ "PropertyChanged",
+ G_VARIANT_TYPE ("(sv)"),
+ G_CALLBACK (context_property_changed),
+ self);
+
+ g_dbus_proxy_call (priv->context_proxy,
+ "SetProperty",
+ g_variant_new ("(sv)",
+ "Active",
+ g_variant_new ("b", TRUE)),
+ G_DBUS_CALL_FLAGS_NONE,
+ 20000,
+ NULL,
+ (GAsyncReadyCallback) stage1_prepare_done,
+ g_object_ref (self));
+}
+
+static void
+do_context_activate (NMModemOfono *self)
+{
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+ g_return_if_fail (NM_IS_MODEM_OFONO (self));
+
+ g_clear_object (&priv->context_proxy);
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ OFONO_DBUS_SERVICE,
+ priv->context_path,
+ OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT,
+ NULL,
+ (GAsyncReadyCallback) context_proxy_new_cb,
+ g_object_ref (self));
+}
+
+static GHashTable *
+create_connect_properties (NMConnection *connection)
+{
+ NMSettingGsm *setting;
+ GHashTable *properties;
+ const char *str;
+
+ setting = nm_connection_get_setting_gsm (connection);
+ properties = g_hash_table_new (g_str_hash, g_str_equal);
+
+ str = nm_setting_gsm_get_apn (setting);
+ if (str)
+ g_hash_table_insert (properties, "AccessPointName", g_strdup (str));
+
+ str = nm_setting_gsm_get_username (setting);
+ if (str)
+ g_hash_table_insert (properties, "Username", g_strdup (str));
+
+ str = nm_setting_gsm_get_password (setting);
+ if (str)
+ g_hash_table_insert (properties, "Password", g_strdup (str));
+
+ return properties;
+}
+
+static NMActStageReturn
+act_stage1_prepare (NMModem *modem,
+ NMConnection *connection,
+ NMDeviceStateReason *reason)
+{
+ NMModemOfono *self = NM_MODEM_OFONO (modem);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+ const char *context_id;
+ char **id = NULL;
+
+ context_id = nm_connection_get_id (connection);
+ id = g_strsplit (context_id, "/", 0);
+ g_assert (id[2]);
+
+ _LOGD ("trying %s %s", id[1], id[2]);
+
+ g_free (priv->context_path);
+ priv->context_path = g_strdup_printf ("%s/%s",
+ nm_modem_get_path (modem),
+ id[2]);
+ g_strfreev (id);
+
+ if (!priv->context_path) {
+ *reason = NM_DEVICE_STATE_REASON_GSM_APN_FAILED;
+ return NM_ACT_STAGE_RETURN_FAILURE;
+ }
+
+ if (priv->connect_properties)
+ g_hash_table_destroy (priv->connect_properties);
+
+ priv->connect_properties = create_connect_properties (connection);
+
+ _LOGI ("activating context %s", priv->context_path);
+
+ if (nm_modem_get_state (modem) == NM_MODEM_STATE_REGISTERED) {
+ do_context_activate (self);
+ } else {
+ _LOGW ("could not activate context: modem is not registered.");
+ *reason = NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER;
+ return NM_ACT_STAGE_RETURN_FAILURE;
+ }
+
+ return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+static void
+modem_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+ gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ priv->modem_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+ if (error) {
+ _LOGE ("failed to create ofono modem DBus proxy: %s", error->message);
+ return;
+ }
+
+ /* Watch for custom ofono PropertyChanged signals */
+ _nm_dbus_signal_connect (priv->modem_proxy,
+ "PropertyChanged",
+ G_VARIANT_TYPE ("(sv)"),
+ G_CALLBACK (modem_property_changed),
+ self);
+
+ g_dbus_proxy_call (priv->modem_proxy,
+ "GetProperties",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ 20000,
+ NULL,
+ (GAsyncReadyCallback) modem_get_properties_done,
+ g_object_ref (self));
+}
+
+static void
+nm_modem_ofono_init (NMModemOfono *self)
+{
+}
+
+static void
+constructed (GObject *object)
+{
+ NMModemOfono *self = NM_MODEM_OFONO (object);
+
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ OFONO_DBUS_SERVICE,
+ nm_modem_get_path (NM_MODEM (self)),
+ OFONO_DBUS_INTERFACE_MODEM,
+ NULL,
+ (GAsyncReadyCallback) modem_proxy_new_cb,
+ g_object_ref (self));
+}
+
+static void
+dispose (GObject *object)
+{
+ NMModemOfono *self = NM_MODEM_OFONO (object);
+ NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+ if (priv->connect_properties) {
+ g_hash_table_destroy (priv->connect_properties);
+ priv->connect_properties = NULL;
+ }
+
+ g_clear_object (&priv->ip4_config);
+
+ if (priv->modem_proxy) {
+ g_signal_handlers_disconnect_by_data (priv->modem_proxy, NM_MODEM_OFONO (self));
+ g_clear_object (&priv->modem_proxy);
+ }
+
+ g_clear_object (&priv->connman_proxy);
+ g_clear_object (&priv->context_proxy);
+
+ if (priv->sim_proxy) {
+ g_signal_handlers_disconnect_by_data (priv->sim_proxy, NM_MODEM_OFONO (self));
+ g_clear_object (&priv->sim_proxy);
+ }
+
+ g_free (priv->imsi);
+ priv->imsi = NULL;
+
+ G_OBJECT_CLASS (nm_modem_ofono_parent_class)->dispose (object);
+}
+
+static void
+nm_modem_ofono_class_init (NMModemOfonoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ NMModemClass *modem_class = NM_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (NMModemOfonoPrivate));
+
+ /* Virtual methods */
+ object_class->constructed = constructed;
+ object_class->dispose = dispose;
+
+ modem_class->get_capabilities = get_capabilities;
+ modem_class->disconnect = disconnect;
+ modem_class->disconnect_finish = disconnect_finish;
+ modem_class->deactivate_cleanup = deactivate_cleanup;
+ modem_class->check_connection_compatible = check_connection_compatible;
+
+ /* same as nm-modem-broadband */
+ modem_class->act_stage1_prepare = act_stage1_prepare;
+
+ /* same as nm-modem-broadband */
+ modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start;
+}
diff --git a/src/devices/wwan/nm-modem-ofono.h b/src/devices/wwan/nm-modem-ofono.h
new file mode 100644
index 0000000000..5965526d61
--- /dev/null
+++ b/src/devices/wwan/nm-modem-ofono.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2013 - Canonical Ltd.
+ */
+
+#ifndef NM_MODEM_OFONO_H
+#define NM_MODEM_OFONO_H
+
+#include "nm-modem.h"
+
+#define NM_TYPE_MODEM_OFONO (nm_modem_ofono_get_type ())
+#define NM_MODEM_OFONO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MODEM_OFONO, NMModemOfono))
+#define NM_IS_MODEM_OFONO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_MODEM_OFONO))
+#define NM_MODEM_OFONO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_MODEM_OFONO, NMModemOfonoClass))
+#define NM_IS_MODEM_OFONO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_MODEM_OFONO))
+#define NM_MODEM_OFONO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_MODEM_OFONO, NMModemOfonoClass))
+
+#define OFONO_DBUS_SERVICE "org.ofono"
+#define OFONO_DBUS_PATH "/"
+#define OFONO_DBUS_INTERFACE "org.ofono.Manager"
+#define OFONO_DBUS_INTERFACE_MODEM "org.ofono.Modem"
+#define OFONO_DBUS_INTERFACE_CONNECTION_MANAGER "org.ofono.ConnectionManager"
+#define OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT "org.ofono.ConnectionContext"
+#define OFONO_DBUS_INTERFACE_SIM_MANAGER "org.ofono.SimManager"
+
+typedef struct {
+ NMModem parent;
+} NMModemOfono;
+
+typedef struct {
+ NMModemClass parent;
+} NMModemOfonoClass;
+
+GType nm_modem_ofono_get_type (void);
+
+NMModem *nm_modem_ofono_new (const char *path);
+
+#endif /* NM_MODEM_OFONO_H */
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index a6c770072c..2e3d63bb83 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -202,7 +202,9 @@ nm_modem_set_mm_enabled (NMModem *self,
return;
}
- NM_MODEM_GET_CLASS (self)->set_mm_enabled (self, enabled);
+ /* Not all modem classes support set_mm_enabled */
+ if (NM_MODEM_GET_CLASS (self)->set_mm_enabled)
+ NM_MODEM_GET_CLASS (self)->set_mm_enabled (self, enabled);
/* Pre-empt the state change signal */
nm_modem_set_state (self,
@@ -573,6 +575,8 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
const char *method;
NMActStageReturn ret;
+ nm_log_dbg (LOGD_MB, "ip4_config_start");
+
g_return_val_if_fail (NM_IS_MODEM (self), NM_ACT_STAGE_RETURN_FAILURE);
g_return_val_if_fail (NM_IS_DEVICE (device), NM_ACT_STAGE_RETURN_FAILURE);
g_return_val_if_fail (NM_IS_DEVICE_CLASS (device_class), NM_ACT_STAGE_RETURN_FAILURE);
@@ -602,9 +606,11 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
ret = ppp_stage3_ip_config_start (self, req, reason);
break;
case NM_MODEM_IP_METHOD_STATIC:
+ nm_log_dbg (LOGD_MB, "MODEM_IP_METHOD_STATIC");
ret = NM_MODEM_GET_CLASS (self)->static_stage3_ip4_config_start (self, req, reason);
break;
case NM_MODEM_IP_METHOD_AUTO:
+ nm_log_dbg (LOGD_MB, "MODEM_IP_METHOD_AUTO");
ret = device_class->act_stage3_ip4_config_start (device, NULL, reason);
break;
default: