diff options
author | Aleksander Morgado <aleksandermj@chromium.org> | 2022-12-08 13:37:55 +0000 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2023-01-03 13:56:25 +0000 |
commit | e14b904cbd6816cb0227d519d308ae71ddaf6e07 (patch) | |
tree | 4997ab68cc606fdf4d72a571e821cec0c8df42ef /src/plugins/mbm | |
parent | 072d7ac9065f444e83b390a1e2af5471ac0d48f6 (diff) |
build: move plugins directory to src/plugins
We are going to allow including the plugin sources built within the
ModemManager daemon binary; moving the sources within the daemon
sources directory makes it easier.
Diffstat (limited to 'src/plugins/mbm')
-rw-r--r-- | src/plugins/mbm/77-mm-ericsson-mbm.rules | 174 | ||||
-rw-r--r-- | src/plugins/mbm/mm-broadband-bearer-mbm.c | 911 | ||||
-rw-r--r-- | src/plugins/mbm/mm-broadband-bearer-mbm.h | 68 | ||||
-rw-r--r-- | src/plugins/mbm/mm-broadband-modem-mbm.c | 1583 | ||||
-rw-r--r-- | src/plugins/mbm/mm-broadband-modem-mbm.h | 58 | ||||
-rw-r--r-- | src/plugins/mbm/mm-modem-helpers-mbm.c | 337 | ||||
-rw-r--r-- | src/plugins/mbm/mm-modem-helpers-mbm.h | 51 | ||||
-rw-r--r-- | src/plugins/mbm/mm-plugin-mbm.c | 101 | ||||
-rw-r--r-- | src/plugins/mbm/mm-plugin-mbm.h | 43 | ||||
-rw-r--r-- | src/plugins/mbm/mm-sim-mbm.c | 242 | ||||
-rw-r--r-- | src/plugins/mbm/mm-sim-mbm.h | 51 | ||||
-rw-r--r-- | src/plugins/mbm/tests/test-modem-helpers-mbm.c | 268 |
12 files changed, 3887 insertions, 0 deletions
diff --git a/src/plugins/mbm/77-mm-ericsson-mbm.rules b/src/plugins/mbm/77-mm-ericsson-mbm.rules new file mode 100644 index 00000000..73e08692 --- /dev/null +++ b/src/plugins/mbm/77-mm-ericsson-mbm.rules @@ -0,0 +1,174 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_mbm_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0bdb", GOTO="mm_mbm_ericsson_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0fce", GOTO="mm_mbm_sony_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="413c", GOTO="mm_mbm_dell_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="03f0", GOTO="mm_mbm_hp_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0930", GOTO="mm_mbm_toshiba_vendorcheck" +GOTO="mm_mbm_end" + +LABEL="mm_mbm_ericsson_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Ericsson F3507g +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1900", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1900", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1902", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1902", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F3607gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1904", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1904", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1905", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1905", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1906", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1906", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F3307 +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190a", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1909", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F3307 R2 +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1914", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C3607w +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1049", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C3607w v2 +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190b", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F5521gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190d", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190d", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1911", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1911", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson H5321gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1919", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson H5321w +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191d", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F5321gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1917", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson F5321w +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191b", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C5621gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191f", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C5621w +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1921", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson H5321gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1926", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1926", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1927", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1927", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C3304w +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1928", ENV{ID_MM_ERICSSON_MBM}="1" + +# Ericsson C5621 TFF +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1936", ENV{ID_MM_ERICSSON_MBM}="1" + +# Lenovo N5321gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="193e", ENV{ID_MM_ERICSSON_MBM}="1" + +GOTO="mm_mbm_end" + +LABEL="mm_mbm_sony_vendorcheck" + +# Sony-Ericsson MD300 +ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d0cf", ENV{ID_MM_ERICSSON_MBM}="1" + +# Sony-Ericsson MD400 +ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d0e1", ENV{ID_MM_ERICSSON_MBM}="1" + +# Sony-Ericsson MD400G +ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d103", ENV{ID_MM_ERICSSON_MBM}="1" + +GOTO="mm_mbm_end" + +LABEL="mm_mbm_dell_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Dell 5560 +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818e", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818e", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" + +# Dell 5550 +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818d", ENV{ID_MM_ERICSSON_MBM}="1" + +# Dell 5530 HSDPA +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{ID_MM_ERICSSON_MBM}="1" + +# Dell F3607gw +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8183", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8183", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8184", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8184", ENV{ID_MM_ERICSSON_MBM}="1" + +# Dell F3307 +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818b", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818b", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818c", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818c", ENV{ID_MM_ERICSSON_MBM}="1" + +GOTO="mm_mbm_end" + +LABEL="mm_mbm_hp_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# HP hs2330 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="271d", ENV{ID_MM_ERICSSON_MBM}="1" + +# HP hs2320 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="261d", ENV{ID_MM_ERICSSON_MBM}="1" + +# HP hs2340 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3a1d", ENV{ID_MM_ERICSSON_MBM}="1" + +# HP hs2350 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3d1d", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3d1d", ENV{ID_MM_ERICSSON_MBM}="1" + +# HP lc2000 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="301d", ENV{ID_MM_ERICSSON_MBM}="1" + +# HP lc2010 Mobile Broadband Module +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="2f1d", ENV{ID_MM_ERICSSON_MBM}="1" + +GOTO="mm_mbm_end" + +LABEL="mm_mbm_toshiba_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Toshiba +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130b", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130b", ENV{ID_MM_ERICSSON_MBM}="1" + +# Toshiba F3607gw +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130c", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130c", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1311", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1311", ENV{ID_MM_ERICSSON_MBM}="1" + +# Toshiba F3307 +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1315", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1316", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1317", ENV{ID_MM_ERICSSON_MBM}="1" + +# Toshiba F5521gw +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1313", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1314", ENV{ID_MM_ERICSSON_MBM}="1" + +# Toshiba H5321gw +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1319", ENV{ID_MM_ERICSSON_MBM}="1" + +GOTO="mm_mbm_end" + +LABEL="mm_mbm_end" diff --git a/src/plugins/mbm/mm-broadband-bearer-mbm.c b/src/plugins/mbm/mm-broadband-bearer-mbm.c new file mode 100644 index 00000000..c1407ab4 --- /dev/null +++ b/src/plugins/mbm/mm-broadband-bearer-mbm.c @@ -0,0 +1,911 @@ +/* -*- 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 - 2010 Ericsson AB + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * Copyright (C) 2017 Aleksander Morgado <aleksander@aleksander.es> + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + * Bjorn Runaker <bjorn.runaker@ericsson.com> + * Torgny Johansson <torgny.johansson@ericsson.com> + * Jonas Sjöquist <jonas.sjoquist@ericsson.com> + * Dan Williams <dcbw@redhat.com> + * Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-mbm.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" +#include "mm-daemon-enums-types.h" + +G_DEFINE_TYPE (MMBroadbandBearerMbm, mm_broadband_bearer_mbm, MM_TYPE_BROADBAND_BEARER) + +struct _MMBroadbandBearerMbmPrivate { + GTask *connect_pending; + GTask *disconnect_pending; +}; + +/*****************************************************************************/ +/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + guint cid; + MMPort *data; + guint poll_count; + guint poll_id; + GError *saved_error; +} Dial3gppContext; + +static void +dial_3gpp_context_free (Dial3gppContext *ctx) +{ + g_assert (!ctx->poll_id); + g_assert (!ctx->saved_error); + g_clear_object (&ctx->data); + g_clear_object (&ctx->primary); + g_clear_object (&ctx->modem); + g_slice_free (Dial3gppContext, ctx); +} + +static MMPort * +dial_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return MM_PORT (g_task_propagate_pointer (G_TASK (res), error)); +} + +static void +connect_reset_ready (MMBroadbandBearer *self, + GAsyncResult *res, + GTask *task) +{ + Dial3gppContext *ctx; + + ctx = g_task_get_task_data (task); + + MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp_finish (self, res, NULL); + + /* When reset is requested, it was either cancelled or an error was stored */ + if (!g_task_return_error_if_cancelled (task)) { + g_assert (ctx->saved_error); + g_task_return_error (task, ctx->saved_error); + ctx->saved_error = NULL; + } + + g_object_unref (task); +} + +static void +connect_reset (GTask *task) +{ + MMBroadbandBearerMbm *self; + Dial3gppContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp ( + MM_BROADBAND_BEARER (self), + MM_BROADBAND_MODEM (ctx->modem), + ctx->primary, + NULL, + ctx->data, + ctx->cid, + (GAsyncReadyCallback) connect_reset_ready, + task); +} + +static void +process_pending_connect_attempt (MMBroadbandBearerMbm *self, + MMBearerConnectionStatus status) +{ + GTask *task; + Dial3gppContext *ctx; + + /* Recover connection task */ + task = self->priv->connect_pending; + self->priv->connect_pending = NULL; + g_assert (task != NULL); + + ctx = g_task_get_task_data (task); + + if (ctx->poll_id) { + g_source_remove (ctx->poll_id); + ctx->poll_id = 0; + } + + /* Received 'CONNECTED' during a connection attempt? */ + if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED) { + /* If we wanted to get cancelled before, do it now. */ + if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) { + connect_reset (task); + return; + } + + g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); + g_object_unref (task); + return; + } + + /* If we wanted to get cancelled before and now we couldn't connect, + * use the cancelled error and return */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + /* Otherwise, received 'DISCONNECTED' during a connection attempt? */ + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Call setup failed"); + g_object_unref (task); +} + +static gboolean connect_poll_cb (MMBroadbandBearerMbm *self); + +static void +connect_poll_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerMbm *self) +{ + GTask *task; + Dial3gppContext *ctx; + GError *error = NULL; + const gchar *response; + guint state; + + task = g_steal_pointer (&self->priv->connect_pending); + + if (!task) { + mm_obj_dbg (self, "connection context was finished already by an unsolicited message"); + /* Run _finish() to finalize the async call, even if we don't care + * the result */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + return; + } + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (!response) { + ctx->saved_error = error; + connect_reset (task); + return; + } + + if (sscanf (response, "*ENAP: %d", &state) == 1 && state == 1) { + /* Success! Connected... */ + g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); + g_object_unref (task); + return; + } + + /* Restore pending task and check again in one second */ + self->priv->connect_pending = task; + g_assert (ctx->poll_id == 0); + ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc) connect_poll_cb, self); +} + +static gboolean +connect_poll_cb (MMBroadbandBearerMbm *self) +{ + GTask *task; + Dial3gppContext *ctx; + + task = g_steal_pointer (&self->priv->connect_pending); + + g_assert (task); + ctx = g_task_get_task_data (task); + + ctx->poll_id = 0; + + /* Complete if we were cancelled */ + if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) { + connect_reset (task); + return G_SOURCE_REMOVE; + } + + /* Too many retries... */ + if (ctx->poll_count > MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT) { + g_assert (!ctx->saved_error); + ctx->saved_error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, + "Connection attempt timed out"); + connect_reset (task); + return G_SOURCE_REMOVE; + } + + /* Restore pending task and poll */ + self->priv->connect_pending = task; + ctx->poll_count++; + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "AT*ENAP?", + 3, + FALSE, + FALSE, /* raw */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)connect_poll_ready, + self); + return G_SOURCE_REMOVE; +} + +static void +activate_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerMbm *self) +{ + GTask *task; + Dial3gppContext *ctx; + GError *error = NULL; + + /* Try to recover the connection context. If none found, it means the + * context was already completed and we have nothing else to do. */ + task = g_steal_pointer (&self->priv->connect_pending); + + if (!task) { + mm_obj_dbg (self, "connection context was finished already by an unsolicited message"); + /* Run _finish() to finalize the async call, even if we don't care + * the result */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + goto out; + } + + /* From now on, if we get cancelled, we'll need to run the connection + * reset ourselves just in case */ + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + goto out; + } + + ctx = g_task_get_task_data (task); + + /* No unsolicited E2NAP status yet; wait for it and periodically poll + * to handle very old F3507g/MD300 firmware that may not send E2NAP. */ + self->priv->connect_pending = task; + ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc)connect_poll_cb, self); + + out: + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); +} + +static void +activate (GTask *task) +{ + MMBroadbandBearerMbm *self; + Dial3gppContext *ctx; + gchar *command; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* The unsolicited response to ENAP may come before the OK does. + * We will keep the connection context in the bearer private data so + * that it is accessible from the unsolicited message handler. */ + g_assert (self->priv->connect_pending == NULL); + self->priv->connect_pending = task; + + /* Activate the PDP context and start the data session */ + command = g_strdup_printf ("AT*ENAP=1,%d", ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 10, + FALSE, + FALSE, /* raw */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)activate_ready, + g_object_ref (self)); /* we pass the bearer object! */ + g_free (command); +} + +static void +authenticate_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + activate (task); +} + +static void +authenticate (GTask *task) +{ + MMBroadbandBearerMbm *self; + Dial3gppContext *ctx; + const gchar *user; + const gchar *password; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + + /* Both user and password are required; otherwise firmware returns an error */ + if (user || password) { + g_autofree gchar *command = NULL; + g_autofree gchar *user_enc = NULL; + g_autofree gchar *password_enc = NULL; + GError *error = NULL; + + user_enc = mm_modem_charset_str_from_utf8 (user, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (ctx->modem)), + FALSE, + &error); + if (!user_enc) { + g_prefix_error (&error, "Couldn't convert user to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + password_enc = mm_modem_charset_str_from_utf8 (password, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (ctx->modem)), + FALSE, + &error); + if (!password_enc) { + g_prefix_error (&error, "Couldn't convert password to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + command = g_strdup_printf ("AT*EIAAUW=%d,1,\"%s\",\"%s\"", + ctx->cid, user_enc, password_enc); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, /* raw */ + g_task_get_cancellable (task), + (GAsyncReadyCallback) authenticate_ready, + task); + return; + } + + mm_obj_dbg (self, "authentication not needed"); + activate (task); +} + +static void +dial_3gpp (MMBroadbandBearer *_self, + MMBaseModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self); + GTask *task; + Dial3gppContext *ctx; + + g_assert (primary != NULL); + + task = g_task_new (self, cancellable, callback, user_data); + + ctx = g_slice_new0 (Dial3gppContext); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + g_task_set_task_data (task, ctx, (GDestroyNotify)dial_3gpp_context_free); + + /* We need a net data port */ + ctx->data = mm_base_modem_get_best_data_port (modem, MM_PORT_TYPE_NET); + if (!ctx->data) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to launch connection"); + g_object_unref (task); + return; + } + + authenticate (task); +} + +/*****************************************************************************/ +/* 3GPP IP config retrieval (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + MMBearerIpFamily family; +} GetIpConfig3gppContext; + +static void +get_ip_config_context_free (GetIpConfig3gppContext *ctx) +{ + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_free (ctx); +} + +static gboolean +get_ip_config_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + MMBearerIpConfig **ipv4_config, + MMBearerIpConfig **ipv6_config, + GError **error) +{ + MMBearerConnectResult *configs; + MMBearerIpConfig *ipv4, *ipv6; + + configs = g_task_propagate_pointer (G_TASK (res), error); + if (!configs) + return FALSE; + + ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs); + ipv6 = mm_bearer_connect_result_peek_ipv6_config (configs); + g_assert (ipv4 || ipv6); + if (ipv4_config && ipv4) + *ipv4_config = g_object_ref (ipv4); + if (ipv6_config && ipv6) + *ipv6_config = g_object_ref (ipv6); + + mm_bearer_connect_result_unref (configs); + return TRUE; +} + +static void +ip_config_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GetIpConfig3gppContext *ctx; + MMBearerIpConfig *ipv4_config = NULL; + MMBearerIpConfig *ipv6_config = NULL; + const gchar *response; + GError *error = NULL; + MMBearerConnectResult *connect_result; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + g_error_free (error); + + /* Fall back to DHCP configuration; early devices don't support *E2IPCFG */ + if (ctx->family == MM_BEARER_IP_FAMILY_IPV4 || ctx->family == MM_BEARER_IP_FAMILY_IPV4V6) { + ipv4_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP); + } + if (ctx->family == MM_BEARER_IP_FAMILY_IPV6 || ctx->family == MM_BEARER_IP_FAMILY_IPV4V6) { + ipv6_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); + } + } else { + if (!mm_mbm_parse_e2ipcfg_response (response, + &ipv4_config, + &ipv6_config, + &error)) { + g_task_return_error (task, error); + goto out; + } + + if (!ipv4_config && !ipv6_config) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get IP config: couldn't parse response '%s'", + response); + goto out; + } + } + + connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->primary), + ipv4_config, + ipv6_config); + g_task_return_pointer (task, + connect_result, + (GDestroyNotify)mm_bearer_connect_result_unref); + +out: + g_object_unref (task); + g_clear_object (&ipv4_config); + g_clear_object (&ipv6_config); +} + +static void +get_ip_config_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + MMBearerIpFamily ip_family, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GetIpConfig3gppContext *ctx; + GTask *task; + + ctx = g_new0 (GetIpConfig3gppContext, 1); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + ctx->family = ip_family; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)get_ip_config_context_free); + + mm_base_modem_at_command_full (MM_BASE_MODEM (modem), + primary, + "*E2IPCFG?", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)ip_config_ready, + task); +} + +/*****************************************************************************/ +/* 3GPP disconnect */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + guint poll_count; + guint poll_id; +} DisconnectContext; + +static void +disconnect_context_free (DisconnectContext *ctx) +{ + g_assert (!ctx->poll_id); + g_clear_object (&ctx->primary); + g_clear_object (&ctx->modem); + g_free (ctx); +} + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +process_pending_disconnect_attempt (MMBroadbandBearerMbm *self, + MMBearerConnectionStatus status) +{ + GTask *task; + DisconnectContext *ctx; + + /* Recover disconnection task */ + task = g_steal_pointer (&self->priv->disconnect_pending); + ctx = g_task_get_task_data (task); + + if (ctx->poll_id) { + g_source_remove (ctx->poll_id); + ctx->poll_id = 0; + } + + /* Received 'DISCONNECTED' during a disconnection attempt? */ + if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) { + mm_obj_dbg (self, "connection disconnect indicated by an unsolicited message"); + g_task_return_boolean (task, TRUE); + } else { + /* Otherwise, report error */ + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Disconnection failed"); + } + g_object_unref (task); +} + +static gboolean disconnect_poll_cb (MMBroadbandBearerMbm *self); + +static void +disconnect_poll_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerMbm *self) + +{ + GTask *task; + DisconnectContext *ctx; + GError *error = NULL; + const gchar *response; + guint state; + + task = g_steal_pointer (&self->priv->disconnect_pending); + + if (!task) { + mm_obj_dbg (self, "disconnection context was finished already by an unsolicited message"); + /* Run _finish() to finalize the async call, even if we don't care + * the result */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + goto out; + } + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + goto out; + } + + if (sscanf (response, "*ENAP: %d", &state) == 1 && state == 0) { + /* Disconnected */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + goto out; + } + + /* Restore pending task and check in 1s */ + self->priv->disconnect_pending = task; + ctx = g_task_get_task_data (task); + g_assert (ctx->poll_id == 0); + ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc) disconnect_poll_cb, self); + + out: + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); +} + +static gboolean +disconnect_poll_cb (MMBroadbandBearerMbm *self) +{ + GTask *task; + DisconnectContext *ctx; + + task = self->priv->disconnect_pending; + self->priv->disconnect_pending = NULL; + + g_assert (task); + ctx = g_task_get_task_data (task); + + ctx->poll_id = 0; + + /* Too many retries... */ + if (ctx->poll_count > MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT) { + g_task_return_new_error (task, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, + "Disconnection attempt timed out"); + g_object_unref (task); + return G_SOURCE_REMOVE; + } + + /* Restore pending task and poll */ + self->priv->disconnect_pending = task; + ctx->poll_count++; + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "AT*ENAP?", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback) disconnect_poll_ready, + g_object_ref (self)); /* we pass the bearer object! */ + return G_SOURCE_REMOVE; +} + +static void +disconnect_enap_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerMbm *self) +{ + DisconnectContext *ctx; + GTask *task; + GError *error = NULL; + + task = g_steal_pointer (&self->priv->disconnect_pending); + + /* Try to recover the disconnection context. If none found, it means the + * context was already completed and we have nothing else to do. */ + if (!task) { + mm_base_modem_at_command_full_finish (modem, res, NULL); + goto out; + } + + ctx = g_task_get_task_data (task); + + /* Ignore errors for now */ + mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + mm_obj_dbg (self, "disconnection failed (not fatal): %s", error->message); + g_error_free (error); + } + + /* No unsolicited E2NAP status yet; wait for it and periodically poll + * to handle very old F3507g/MD300 firmware that may not send E2NAP. */ + self->priv->disconnect_pending = task; + ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc)disconnect_poll_cb, self); + + out: + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); +} + +static void +disconnect_3gpp (MMBroadbandBearer *_self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self); + GTask *task; + DisconnectContext *ctx; + + g_assert (primary != NULL); + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_new0 (DisconnectContext, 1); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + g_task_set_task_data (task, ctx, (GDestroyNotify) disconnect_context_free); + + /* The unsolicited response to ENAP may come before the OK does. + * We will keep the disconnection context in the bearer private data so + * that it is accessible from the unsolicited message handler. */ + g_assert (self->priv->disconnect_pending == NULL); + self->priv->disconnect_pending = task; + + mm_base_modem_at_command_full (MM_BASE_MODEM (modem), + primary, + "*ENAP=0", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)disconnect_enap_ready, + g_object_ref (self)); /* we pass the bearer object! */ +} + +/*****************************************************************************/ + +static void +report_connection_status (MMBaseBearer *_self, + MMBearerConnectionStatus status, + const GError *connection_error) +{ + MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self); + + g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED || + status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED || + status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED); + + /* Process pending connection attempt */ + if (self->priv->connect_pending) { + process_pending_connect_attempt (self, status); + return; + } + + /* Process pending disconnection attempt */ + if (self->priv->disconnect_pending) { + process_pending_disconnect_attempt (self, status); + return; + } + + mm_obj_dbg (self, "received spontaneous E2NAP (%s)", + mm_bearer_connection_status_get_string (status)); + + /* Received a random 'DISCONNECTED'...*/ + if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED || + status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED) { + /* If no connection/disconnection attempt on-going, make sure we mark ourselves as + * disconnected. Make sure we only pass 'DISCONNECTED' to the parent */ + MM_BASE_BEARER_CLASS (mm_broadband_bearer_mbm_parent_class)->report_connection_status ( + _self, + MM_BEARER_CONNECTION_STATUS_DISCONNECTED, + NULL); + } +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_broadband_bearer_mbm_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *bearer; + GObject *source; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_mbm_new (MMBroadbandModemMbm *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_MBM, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + NULL); +} + +static void +mm_broadband_bearer_mbm_init (MMBroadbandBearerMbm *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_BEARER_MBM, + MMBroadbandBearerMbmPrivate); +} + +static void +mm_broadband_bearer_mbm_class_init (MMBroadbandBearerMbmClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandBearerMbmPrivate)); + + base_bearer_class->report_connection_status = report_connection_status; + base_bearer_class->load_connection_status = NULL; + base_bearer_class->load_connection_status_finish = NULL; +#if defined WITH_SUSPEND_RESUME + base_bearer_class->reload_connection_status = NULL; + base_bearer_class->reload_connection_status_finish = NULL; +#endif + + broadband_bearer_class->dial_3gpp = dial_3gpp; + broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; + broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp; + broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; +} diff --git a/src/plugins/mbm/mm-broadband-bearer-mbm.h b/src/plugins/mbm/mm-broadband-bearer-mbm.h new file mode 100644 index 00000000..a05b456c --- /dev/null +++ b/src/plugins/mbm/mm-broadband-bearer-mbm.h @@ -0,0 +1,68 @@ +/* -*- 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 - 2010 Ericsson AB + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + * Bjorn Runaker <bjorn.runaker@ericsson.com> + * Torgny Johansson <torgny.johansson@ericsson.com> + * Jonas Sjöquist <jonas.sjoquist@ericsson.com> + * Dan Williams <dcbw@redhat.com> + * Aleksander Morgado <aleksander@lanedo.com> + */ + +#ifndef MM_BROADBAND_BEARER_MBM_H +#define MM_BROADBAND_BEARER_MBM_H + +#include <glib.h> +#include <glib-object.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-mbm.h" + +#define MM_TYPE_BROADBAND_BEARER_MBM (mm_broadband_bearer_mbm_get_type ()) +#define MM_BROADBAND_BEARER_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbm)) +#define MM_BROADBAND_BEARER_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbmClass)) +#define MM_IS_BROADBAND_BEARER_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_MBM)) +#define MM_IS_BROADBAND_BEARER_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_MBM)) +#define MM_BROADBAND_BEARER_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbmClass)) + +typedef struct _MMBroadbandBearerMbm MMBroadbandBearerMbm; +typedef struct _MMBroadbandBearerMbmClass MMBroadbandBearerMbmClass; +typedef struct _MMBroadbandBearerMbmPrivate MMBroadbandBearerMbmPrivate; + +struct _MMBroadbandBearerMbm { + MMBroadbandBearer parent; + MMBroadbandBearerMbmPrivate *priv; +}; + +struct _MMBroadbandBearerMbmClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_mbm_get_type (void); + +/* Default 3GPP bearer creation implementation */ +void mm_broadband_bearer_mbm_new (MMBroadbandModemMbm *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_mbm_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_BROADBAND_BEARER_MBM_H */ diff --git a/src/plugins/mbm/mm-broadband-modem-mbm.c b/src/plugins/mbm/mm-broadband-modem-mbm.c new file mode 100644 index 00000000..fbc9830c --- /dev/null +++ b/src/plugins/mbm/mm-broadband-modem-mbm.c @@ -0,0 +1,1583 @@ +/* -*- 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 - 2010 Ericsson AB + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + * Bjorn Runaker <bjorn.runaker@ericsson.com> + * Torgny Johansson <torgny.johansson@ericsson.com> + * Jonas Sjöquist <jonas.sjoquist@ericsson.com> + * Dan Williams <dcbw@redhat.com> + * Aleksander Morgado <aleksander@lanedo.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-bearer-list.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" +#include "mm-broadband-modem-mbm.h" +#include "mm-broadband-bearer-mbm.h" +#include "mm-sim-mbm.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-location.h" + +/* sets the interval in seconds on how often the card emits the NMEA sentences */ +#define MBM_GPS_NMEA_INTERVAL "5" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbm, mm_broadband_modem_mbm, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)) + +#define MBM_E2NAP_DISCONNECTED 0 +#define MBM_E2NAP_CONNECTED 1 +#define MBM_E2NAP_CONNECTING 2 + +struct _MMBroadbandModemMbmPrivate { + gboolean have_emrdy; + + GRegex *e2nap_regex; + GRegex *e2nap_ext_regex; + GRegex *emrdy_regex; + GRegex *pacsp_regex; + GRegex *estksmenu_regex; + GRegex *estksms_regex; + GRegex *emwi_regex; + GRegex *erinfo_regex; + + MMModemLocationSource enabled_sources; + + guint mbm_mode; +}; + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_mbm_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_mbm_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + + g_object_unref (task); +} + +static void +modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_obj_dbg (self, "creating MBM bearer..."); + mm_broadband_bearer_mbm_new (MM_BROADBAND_MODEM_MBM (self), + properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_mbm_new_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Create SIM (Modem interface) */ + +static MMBaseSim * +create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_mbm_new_finish (res, error); +} + +static void +create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* New MBM SIM */ + mm_sim_mbm_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + +/*****************************************************************************/ +/* After SIM unlock (Modem interface) */ + +static gboolean +modem_after_sim_unlock_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +after_sim_unlock_wait_cb (GTask *task) +{ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +modem_after_sim_unlock (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* wait so sim pin is done */ + g_timeout_add (500, (GSourceFunc)after_sim_unlock_wait_cb, task); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *_self, + GAsyncResult *res, + GError **error) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + const gchar *response; + guint32 mask = 0; + GArray *combinations; + MMModemModeCombination mode; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + if (!mm_mbm_parse_cfun_test (response, self, &mask, error)) + return FALSE; + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3); + + /* 2G only */ + if (mask & (1 << MBM_NETWORK_MODE_2G)) { + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + + /* 3G only */ + if (mask & (1 << MBM_NETWORK_MODE_3G)) { + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + + /* 2G and 3G */ + if (mask & (1 << MBM_NETWORK_MODE_ANY)) { + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + + if (combinations->len == 0) { + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't load any supported mode"); + g_array_unref (combinations); + return NULL; + } + + return combinations; +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load initial allowed/preferred modes (Modem interface) */ + +static gboolean +load_current_modes_finish (MMIfaceModem *_self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + const gchar *response; + gint mbm_mode = -1; + + g_assert (allowed); + g_assert (preferred); + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response || !mm_mbm_parse_cfun_query_current_modes (response, allowed, &mbm_mode, error)) + return FALSE; + + /* No settings to set preferred */ + *preferred = MM_MODEM_MODE_NONE; + + if (mbm_mode != -1) + self->priv->mbm_mode = mbm_mode; + + return TRUE; +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +typedef struct { + gint mbm_mode; +} SetCurrentModesContext; + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +allowed_mode_update_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + SetCurrentModesContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_finish (self, res, &error); + if (error) + /* Let the error be critical. */ + g_task_return_error (task, error); + else { + /* Cache current allowed mode */ + MM_BROADBAND_MODEM_MBM (self)->priv->mbm_mode = ctx->mbm_mode; + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SetCurrentModesContext *ctx; + GTask *task; + gchar *command; + + ctx = g_new (SetCurrentModesContext, 1); + ctx->mbm_mode = -1; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + if (allowed == MM_MODEM_MODE_2G) + ctx->mbm_mode = MBM_NETWORK_MODE_2G; + else if (allowed == MM_MODEM_MODE_3G) + ctx->mbm_mode = MBM_NETWORK_MODE_3G; + else if ((allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G) || + allowed == MM_MODEM_MODE_ANY) && + preferred == MM_MODEM_MODE_NONE) + ctx->mbm_mode = MBM_NETWORK_MODE_ANY; + + if (ctx->mbm_mode < 0) { + gchar *allowed_str; + gchar *preferred_str; + + allowed_str = mm_modem_mode_build_string_from_mask (allowed); + preferred_str = mm_modem_mode_build_string_from_mask (preferred); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Requested mode (allowed: '%s', preferred: '%s') not " + "supported by the modem.", + allowed_str, + preferred_str); + g_object_unref (task); + g_free (allowed_str); + g_free (preferred_str); + return; + } + + command = g_strdup_printf ("+CFUN=%d", ctx->mbm_mode); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)allowed_mode_update_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Initializing the modem (during first enabling) */ + +static gboolean +enabling_modem_init_finish (MMBroadbandModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +enabling_init_sequence_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + /* Ignore errors */ + mm_base_modem_at_sequence_full_finish (self, res, NULL, NULL); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static const MMBaseModemAtCommand enabling_modem_init_sequence[] = { + /* Init command */ + { "&F", 3, FALSE, NULL }, + /* Ensure disconnected */ + { "*ENAP=0", 3, FALSE, NULL }, + { NULL } +}; + +static void +run_enabling_init_sequence (GTask *task) +{ + MMBaseModem *self; + + self = g_task_get_source_object (task); + mm_base_modem_at_sequence_full (self, + mm_base_modem_peek_port_primary (self), + enabling_modem_init_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)enabling_init_sequence_ready, + task); +} + +static void +emrdy_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + /* EMRDY unsolicited response might have happened between the command + * submission and the response. This was seen once: + * + * (ttyACM0): --> 'AT*EMRDY?<CR>' + * (ttyACM0): <-- 'T*EMRD<CR><LF>*EMRDY: 1<CR><LF>Y?' + * + * So suppress the warning if the unsolicited handler handled the response + * before we get here. + */ + if (!mm_base_modem_at_command_finish (self, res, &error)) { + if (g_error_matches (error, + MM_SERIAL_ERROR, + MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) + mm_obj_warn (self, "timed out waiting for EMRDY response"); + else + MM_BROADBAND_MODEM_MBM (self)->priv->have_emrdy = TRUE; + g_error_free (error); + } + + run_enabling_init_sequence (task); +} + +static void +enabling_modem_init (MMBroadbandModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Modem is ready?, no need to check EMRDY */ + if (self->priv->have_emrdy) { + run_enabling_init_sequence (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*EMRDY?", + 3, + FALSE, + (GAsyncReadyCallback)emrdy_ready, + task); +} + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Use AT+CFUN=4 for power down. It will stop the RF (IMSI detach), and + * keeps access to the SIM */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN=4", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Powering up the modem (Modem interface) */ + +static gboolean +modem_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + /* By default, errors in the power up command are ignored. */ + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL); + return TRUE; +} + +static void +modem_power_up (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + gchar *command; + + g_assert (self->priv->mbm_mode == MBM_NETWORK_MODE_ANY || + self->priv->mbm_mode == MBM_NETWORK_MODE_2G || + self->priv->mbm_mode == MBM_NETWORK_MODE_3G); + + command = g_strdup_printf ("+CFUN=%u", self->priv->mbm_mode); + mm_base_modem_at_command (MM_BASE_MODEM (self), + command, + 5, + FALSE, + callback, + user_data); + g_free (command); +} + +/*****************************************************************************/ +/* Power state loading (Modem interface) */ + +static MMModemPowerState +load_power_state_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + const gchar *response; + MMModemPowerState state; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response || !mm_mbm_parse_cfun_query_power_state (response, &state, error)) + return MM_MODEM_POWER_STATE_UNKNOWN; + + return state; +} + +static void +load_power_state (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CFUN?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Reset (Modem interface) */ + +static gboolean +reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + /* Ignore errors */ + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL); + return TRUE; +} + +static void +reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*E2RESET", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Factory reset (Modem interface) */ + +static gboolean +factory_reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + /* Ignore errors */ + mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, NULL); + return TRUE; +} + +static const MMBaseModemAtCommand factory_reset_sequence[] = { + /* Init command */ + { "&F +CMEE=0", 3, FALSE, NULL }, + { "+COPS=0", 3, FALSE, NULL }, + { "+CR=0", 3, FALSE, NULL }, + { "+CRC=0", 3, FALSE, NULL }, + { "+CREG=0", 3, FALSE, NULL }, + { "+CMER=0", 3, FALSE, NULL }, + { "*EPEE=0", 3, FALSE, NULL }, + { "+CNMI=2, 0, 0, 0, 0", 3, FALSE, NULL }, + { "+CGREG=0", 3, FALSE, NULL }, + { "*EIAD=0", 3, FALSE, NULL }, + { "+CGSMS=3", 3, FALSE, NULL }, + { "+CSCA=\"\",129", 3, FALSE, NULL }, + { NULL } +}; + +static void +factory_reset (MMIfaceModem *self, + const gchar *code, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_obj_dbg (self, "ignoring user-provided factory reset code: '%s'", code); + + mm_base_modem_at_sequence (MM_BASE_MODEM (self), + factory_reset_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + callback, + user_data); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + MMUnlockRetries *unlock_retries; + const gchar *response; + gint matched; + guint a, b, c ,d; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return NULL; + + matched = sscanf (response, "*EPIN: %d, %d, %d, %d", + &a, &b, &c, &d); + if (matched != 4) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Could not parse PIN retries results: '%s'", + response); + return NULL; + } + + if (a > 998) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid PIN attempts left: '%u'", + a); + return NULL; + } + + unlock_retries = mm_unlock_retries_new (); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN, a); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK, b); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN2, c); + mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK2, d); + return unlock_retries; +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "*EPIN?", + 10, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +typedef struct { + MMBearerConnectionStatus status; +} BearerListReportStatusForeachContext; + +static void +bearer_list_report_status_foreach (MMBaseBearer *bearer, + BearerListReportStatusForeachContext *ctx) +{ + mm_base_bearer_report_connection_status (bearer, ctx->status); +} + +static void +e2nap_received (MMPortSerialAt *port, + GMatchInfo *info, + MMBroadbandModemMbm *self) +{ + MMBearerList *list = NULL; + guint state; + BearerListReportStatusForeachContext ctx; + + if (!mm_get_uint_from_match_info (info, 1, &state)) + return; + + ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN; + + switch (state) { + case MBM_E2NAP_DISCONNECTED: + mm_obj_dbg (self, "disconnected"); + ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED; + break; + case MBM_E2NAP_CONNECTED: + mm_obj_dbg (self, "connected"); + ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED; + break; + case MBM_E2NAP_CONNECTING: + mm_obj_dbg (self, "connecting"); + break; + default: + /* Should not happen */ + mm_obj_dbg (self, "unhandled E2NAP state %d", state); + } + + /* If unknown status, don't try to report anything */ + if (ctx.status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) + return; + + /* If empty bearer list, nothing else to do */ + g_object_get (self, + MM_IFACE_MODEM_BEARER_LIST, &list, + NULL); + if (!list) + return; + + mm_bearer_list_foreach (list, + (MMBearerListForeachFunc)bearer_list_report_status_foreach, + &ctx); + g_object_unref (list); +} + +static void +erinfo_received (MMPortSerialAt *port, + GMatchInfo *info, + MMBroadbandModemMbm *self) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + guint mode; + + if (mm_get_uint_from_match_info (info, 2, &mode)) { + switch (mode) { + case 1: + act = MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + break; + case 2: + act = MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + break; + default: + break; + } + } + + /* 3G modes take precedence */ + if (mm_get_uint_from_match_info (info, 3, &mode)) { + switch (mode) { + case 1: + act = MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + break; + case 2: + act = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; + break; + case 3: + act = MM_MODEM_ACCESS_TECHNOLOGY_HSPA; + break; + default: + break; + } + } + + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + act, + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); +} + +static void +set_unsolicited_events_handlers (MMBroadbandModemMbm *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + guint i; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* Access technology related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->erinfo_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)erinfo_received : NULL, + enable ? self : NULL, + NULL); + + /* Connection related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->e2nap_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)e2nap_received : NULL, + enable ? self : NULL, + NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->e2nap_ext_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)e2nap_received : NULL, + enable ? self : NULL, + NULL); + } +} + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +static void +parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own cleanup first */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Enabling unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +own_enable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static const MMBaseModemAtCommand unsolicited_enable_sequence[] = { + { "*ERINFO=1", 5, FALSE, NULL }, + { "*E2NAP=1", 5, FALSE, NULL }, + { NULL } +}; + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Our own enable now */ + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_enable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_enable_unsolicited_events_ready, + task); +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's enable */ + iface_modem_3gpp_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Disabling unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +own_disable_unsolicited_events_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Next, chain up parent's disable */ + iface_modem_3gpp_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, + task); +} + +static const MMBaseModemAtCommand unsolicited_disable_sequence[] = { + { "*ERINFO=0", 5, FALSE, NULL }, + { "*E2NAP=0", 5, FALSE, NULL }, + { NULL } +}; + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Our own disable first */ + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_disable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_disable_unsolicited_events_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +static MMModemLocationSource +location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + + sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* not sure how to check if GPS is supported, just allow it */ + if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) + sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED); + + /* So we're done, complete */ + g_task_return_int (task, sources); + g_object_unref (task); +} + +static void +location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_location_parent->load_capabilities ( + self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Enable/Disable location gathering (Location interface) */ + +typedef struct { + MMModemLocationSource source; +} LocationGatheringContext; + +/******************************/ +/* Disable location gathering */ + +static gboolean +disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LocationGatheringContext *ctx; + MMPortSerialGps *gps_port; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_full_finish (self, res, &error); + + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + /* Even if we get an error here, we try to close the GPS port */ + gps_port = mm_base_modem_peek_port_gps (self); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +disable_location_gathering (MMIfaceModemLocation *_self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + gboolean stop_gps = FALSE; + LocationGatheringContext *ctx; + GTask *task; + + ctx = g_new (LocationGatheringContext, 1); + ctx->source = source; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + /* Only stop GPS engine if no GPS-related sources enabled */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + self->priv->enabled_sources &= ~source; + + if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + stop_gps = TRUE; + } + + if (stop_gps) { + mm_base_modem_at_command_full (MM_BASE_MODEM (_self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (_self)), + "AT*E2GPSCTL=0", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_disabled_ready, + task); + return; + } + + /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +static gboolean +enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_enabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LocationGatheringContext *ctx; + GError *error = NULL; + MMPortSerialGps *gps_port; + + if (!mm_base_modem_at_command_full_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + gps_port = mm_base_modem_peek_port_gps (self); + if (!gps_port || + !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't open raw GPS serial port"); + } else { + GByteArray *buf; + const gchar *command = "ATE0*E2GPSNPD\r\n"; + + /* We need to send an AT command to the GPS data port to + * toggle it into this data mode. This is a particularity of + * mbm cards where the GPS data port is not hard wired. So + * we need to use the MMPortSerial API here. + */ + buf = g_byte_array_new (); + g_byte_array_append (buf, (const guint8 *) command, strlen (command)); + mm_port_serial_command (MM_PORT_SERIAL (gps_port), + buf, + 3, + FALSE, /* never cached */ + FALSE, /* always queued last */ + NULL, + NULL, + NULL); + g_byte_array_unref (buf); + g_task_return_boolean (task, TRUE); + } + + } else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + LocationGatheringContext *ctx; + gboolean start_gps = FALSE; + GError *error = NULL; + + if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Now our own enabling */ + + /* NMEA and RAW are both enabled in the same way */ + ctx = g_task_get_task_data (task); + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + /* Only start GPS engine if not done already */ + if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + start_gps = TRUE; + self->priv->enabled_sources |= ctx->source; + } + + if (start_gps) { + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + "AT*E2GPSCTL=1," MBM_GPS_NMEA_INTERVAL ",0", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_enabled_ready, + task); + return; + } + + /* For any other location (e.g. 3GPP), or if GPS already running just return */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LocationGatheringContext *ctx; + GTask *task; + + ctx = g_new (LocationGatheringContext, 1); + ctx->source = source; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + /* Chain up parent's gathering enable */ + iface_modem_location_parent->enable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +emrdy_received (MMPortSerialAt *port, + GMatchInfo *info, + MMBroadbandModemMbm *self) +{ + self->priv->have_emrdy = TRUE; +} + +static void +gps_trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +static void +setup_ports (MMBroadbandModem *_self) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + MMPortSerialAt *ports[2]; + MMPortSerialGps *gps_data_port; + guint i; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbm_parent_class)->setup_ports (_self); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Setup unsolicited handlers which should be always on */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* The Ericsson modems always have a free AT command port, so we + * don't need to flash the ports when disconnecting to get back to + * command mode. F5521gw R2A07 resets port properties like echo when + * flashed, leading to confusion. bgo #650740 + */ + g_object_set (G_OBJECT (ports[i]), + MM_PORT_SERIAL_FLASH_OK, FALSE, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->emrdy_regex, + (MMPortSerialAtUnsolicitedMsgFn)emrdy_received, + self, + NULL); + + /* Several unsolicited messages to always ignore... */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->pacsp_regex, + NULL, NULL, NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->estksmenu_regex, + NULL, NULL, NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->estksms_regex, + NULL, NULL, NULL); + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->emwi_regex, + NULL, NULL, NULL); + } + + /* Now reset the unsolicited messages we'll handle when enabled */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), FALSE); + + /* NMEA GPS monitoring */ + gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_data_port) { + /* make sure GPS is stopped incase it was left enabled */ + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + "AT*E2GPSCTL=0", + 3, FALSE, FALSE, NULL, NULL, NULL); + /* Add handler for the NMEA traces */ + mm_port_serial_gps_add_trace_handler (gps_data_port, + (MMPortSerialGpsTraceFn)gps_trace_received, + self, NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemMbm * +mm_broadband_modem_mbm_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBM, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* MBM bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + NULL); +} + +static void +mm_broadband_modem_mbm_init (MMBroadbandModemMbm *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_MBM, + MMBroadbandModemMbmPrivate); + + /* Prepare regular expressions to setup */ + self->priv->e2nap_regex = g_regex_new ("\\r\\n\\*E2NAP: (\\d)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->e2nap_ext_regex = g_regex_new ("\\r\\n\\*E2NAP: (\\d),.*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->emrdy_regex = g_regex_new ("\\r\\n\\*EMRDY: \\d\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->pacsp_regex = g_regex_new ("\\r\\n\\+PACSP(\\d)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->estksmenu_regex = g_regex_new ("\\R\\*ESTKSMENU:.*\\R", + G_REGEX_RAW | G_REGEX_OPTIMIZE | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, G_REGEX_MATCH_NEWLINE_CRLF, NULL); + self->priv->estksms_regex = g_regex_new ("\\r\\n\\*ESTKSMS:.*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->emwi_regex = g_regex_new ("\\r\\n\\*EMWI: (\\d),(\\d).*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->erinfo_regex = g_regex_new ("\\r\\n\\*ERINFO:\\s*(\\d),(\\d),(\\d).*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + self->priv->mbm_mode = MBM_NETWORK_MODE_ANY; +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (object); + + g_regex_unref (self->priv->e2nap_regex); + g_regex_unref (self->priv->e2nap_ext_regex); + g_regex_unref (self->priv->emrdy_regex); + g_regex_unref (self->priv->pacsp_regex); + g_regex_unref (self->priv->estksmenu_regex); + g_regex_unref (self->priv->estksms_regex); + g_regex_unref (self->priv->emwi_regex); + g_regex_unref (self->priv->erinfo_regex); + + G_OBJECT_CLASS (mm_broadband_modem_mbm_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; + iface->create_sim = create_sim; + iface->create_sim_finish = create_sim_finish; + iface->modem_after_sim_unlock = modem_after_sim_unlock; + iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->reset = reset; + iface->reset_finish = reset_finish; + iface->factory_reset = factory_reset; + iface->factory_reset_finish = factory_reset_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; + iface->load_power_state = load_power_state; + iface->load_power_state_finish = load_power_state_finish; + iface->modem_power_up = modem_power_up; + iface->modem_power_up_finish = modem_power_up_finish; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish; + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = location_load_capabilities; + iface->load_capabilities_finish = location_load_capabilities_finish; + iface->enable_location_gathering = enable_location_gathering; + iface->enable_location_gathering_finish = enable_location_gathering_finish; + iface->disable_location_gathering = disable_location_gathering; + iface->disable_location_gathering_finish = disable_location_gathering_finish; +} + +static void +mm_broadband_modem_mbm_class_init (MMBroadbandModemMbmClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemMbmPrivate)); + + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; + broadband_modem_class->enabling_modem_init = enabling_modem_init; + broadband_modem_class->enabling_modem_init_finish = enabling_modem_init_finish; +} diff --git a/src/plugins/mbm/mm-broadband-modem-mbm.h b/src/plugins/mbm/mm-broadband-modem-mbm.h new file mode 100644 index 00000000..21eeaef5 --- /dev/null +++ b/src/plugins/mbm/mm-broadband-modem-mbm.h @@ -0,0 +1,58 @@ +/* -*- 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 - 2010 Ericsson AB + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + * Bjorn Runaker <bjorn.runaker@ericsson.com> + * Torgny Johansson <torgny.johansson@ericsson.com> + * Jonas Sjöquist <jonas.sjoquist@ericsson.com> + * Dan Williams <dcbw@redhat.com> + * Aleksander Morgado <aleksander@lanedo.com> + */ + +#ifndef MM_BROADBAND_MODEM_MBM_H +#define MM_BROADBAND_MODEM_MBM_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_MBM (mm_broadband_modem_mbm_get_type ()) +#define MM_BROADBAND_MODEM_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbm)) +#define MM_BROADBAND_MODEM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbmClass)) +#define MM_IS_BROADBAND_MODEM_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBM)) +#define MM_IS_BROADBAND_MODEM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBM)) +#define MM_BROADBAND_MODEM_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbmClass)) + +typedef struct _MMBroadbandModemMbm MMBroadbandModemMbm; +typedef struct _MMBroadbandModemMbmClass MMBroadbandModemMbmClass; +typedef struct _MMBroadbandModemMbmPrivate MMBroadbandModemMbmPrivate; + +struct _MMBroadbandModemMbm { + MMBroadbandModem parent; + MMBroadbandModemMbmPrivate *priv; +}; + +struct _MMBroadbandModemMbmClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_mbm_get_type (void); + +MMBroadbandModemMbm *mm_broadband_modem_mbm_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_MBM_H */ diff --git a/src/plugins/mbm/mm-modem-helpers-mbm.c b/src/plugins/mbm/mm-modem-helpers-mbm.c new file mode 100644 index 00000000..846cc4d6 --- /dev/null +++ b/src/plugins/mbm/mm-modem-helpers-mbm.c @@ -0,0 +1,337 @@ +/* -*- 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) 2012 Google, Inc. + * Copyright (C) 2012 - 2013 Aleksander Morgado <aleksander@gnu.org> + * Copyright (C) 2014 Dan Williams <dcbw@redhat.com> + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" + +/*****************************************************************************/ +/* *E2IPCFG response parser */ + +static gboolean +validate_address (int family, const char *addr) +{ + struct in6_addr tmp6 = IN6ADDR_ANY_INIT; + + if (inet_pton (family, addr, (void *) &tmp6) != 1) +{ +g_message ("%s: famil '%s'", __func__, addr); + return FALSE; +} + if ((family == AF_INET6) && IN6_IS_ADDR_UNSPECIFIED (&tmp6)) + return FALSE; + return TRUE; +} + +#define E2IPCFG_TAG "*E2IPCFG" + +gboolean +mm_mbm_parse_e2ipcfg_response (const gchar *response, + MMBearerIpConfig **out_ip4_config, + MMBearerIpConfig **out_ip6_config, + GError **error) +{ + MMBearerIpConfig **ip_config = NULL; + gboolean got_address = FALSE; + gboolean got_gw = FALSE; + gboolean got_dns = FALSE; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *match_error = NULL; + gchar *dns[3] = { 0 }; + guint dns_idx = 0; + int family = AF_INET; + MMBearerIpMethod method = MM_BEARER_IP_METHOD_STATIC; + + g_return_val_if_fail (out_ip4_config, FALSE); + g_return_val_if_fail (out_ip6_config, FALSE); + + if (!response || !g_str_has_prefix (response, E2IPCFG_TAG)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing " E2IPCFG_TAG " prefix"); + return FALSE; + } + + response = mm_strip_tag (response, "*E2IPCFG: "); + + if (strchr (response, ':')) { + family = AF_INET6; + ip_config = out_ip6_config; + method = MM_BEARER_IP_METHOD_DHCP; + } else if (strchr (response, '.')) { + family = AF_INET; + ip_config = out_ip4_config; + method = MM_BEARER_IP_METHOD_STATIC; + } else { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to detect " E2IPCFG_TAG " address family"); + return FALSE; + } + + /* *E2IPCFG: (1,<IP>)(2,<gateway>)(3,<DNS>)(3,<DNS>) + * + * *E2IPCFG: (1,"46.157.32.246")(2,"46.157.32.243")(3,"193.213.112.4")(3,"130.67.15.198") + * *E2IPCFG: (1,"fe80:0000:0000:0000:0000:0000:e537:1801")(3,"2001:4600:0004:0fff:0000:0000:0000:0054")(3,"2001:4600:0004:1fff:0000:0000:0000:0054") + * *E2IPCFG: (1,"fe80:0000:0000:0000:0000:0027:b7fe:9401")(3,"fd00:976a:0000:0000:0000:0000:0000:0009") + */ + r = g_regex_new ("\\((\\d),\"([0-9a-fA-F.:]+)\"\\)", 0, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse " E2IPCFG_TAG " results: "); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match " E2IPCFG_TAG " reply"); + } + return FALSE; + } + + *ip_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (*ip_config, method); + while (g_match_info_matches (match_info)) { + g_autofree gchar *id = NULL; + g_autofree gchar *str = NULL; + + id = g_match_info_fetch (match_info, 1); + str = g_match_info_fetch (match_info, 2); + + switch (atoi (id)) { + case 1: + if (validate_address (family, str)) { + mm_bearer_ip_config_set_address (*ip_config, str); + mm_bearer_ip_config_set_prefix (*ip_config, (family == AF_INET6) ? 64 : 28); + got_address = TRUE; + } + break; + case 2: + if ((family == AF_INET) && validate_address (family, str)) { + mm_bearer_ip_config_set_gateway (*ip_config, str); + got_gw = TRUE; + } + break; + case 3: + if (validate_address (family, str)) { + dns[dns_idx++] = g_strdup (str); + got_dns = TRUE; + } + break; + default: + break; + } + g_match_info_next (match_info, NULL); + } + + if (got_dns) { + mm_bearer_ip_config_set_dns (*ip_config, (const gchar **) dns); + g_free (dns[0]); + g_free (dns[1]); + } + + if (!got_address || (family == AF_INET && !got_gw)) { + g_object_unref (*ip_config); + *ip_config = NULL; + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Got incomplete IP configuration from " E2IPCFG_TAG); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +#define CFUN_TAG "+CFUN:" + +static void +add_supported_mode (guint mode, + gpointer log_object, + guint32 *mask) +{ + g_assert (mask); + if (mode >= 32) + mm_obj_warn (log_object, "ignored unexpected mode in +CFUN match: %d", mode); + else + *mask |= (1 << mode); +} + +gboolean +mm_mbm_parse_cfun_test (const gchar *response, + gpointer log_object, + guint32 *supported_mask, + GError **error) +{ + gchar **groups; + guint32 mask = 0; + + g_assert (supported_mask); + + if (!response || !g_str_has_prefix (response, CFUN_TAG)) { + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Missing " CFUN_TAG " prefix"); + return FALSE; + } + + /* + * AT+CFUN=? + * +CFUN: (0,1,4-6),(0,1) + * OK + */ + + /* Strip tag from response */ + response = mm_strip_tag (response, CFUN_TAG); + + /* Split response in (groups) */ + groups = mm_split_string_groups (response); + + /* First group is the one listing supported modes */ + if (groups && groups[0]) { + gchar **supported_modes; + + supported_modes = g_strsplit_set (groups[0], ", ", -1); + if (supported_modes) { + guint i; + + for (i = 0; supported_modes[i]; i++) { + gchar *separator; + guint mode; + + if (!supported_modes[i][0]) + continue; + + /* Check if this is a range that's being given to us */ + separator = strchr (supported_modes[i], '-'); + if (separator) { + gchar *first_str; + gchar *last_str; + guint first; + guint last; + + *separator = '\0'; + first_str = supported_modes[i]; + last_str = separator + 1; + + if (!mm_get_uint_from_str (first_str, &first)) + mm_obj_warn (log_object, "couldn't match range start: '%s'", first_str); + else if (!mm_get_uint_from_str (last_str, &last)) + mm_obj_warn (log_object, "couldn't match range stop: '%s'", last_str); + else if (first >= last) + mm_obj_warn (log_object, "couldn't match range: wrong first '%s' and last '%s' items", first_str, last_str); + else { + for (mode = first; mode <= last; mode++) + add_supported_mode (mode, log_object, &mask); + } + } else { + if (!mm_get_uint_from_str (supported_modes[i], &mode)) + mm_obj_warn (log_object, "couldn't match mode: '%s'", supported_modes[i]); + else + add_supported_mode (mode, log_object, &mask); + } + } + + g_strfreev (supported_modes); + } + } + g_strfreev (groups); + + if (mask) + *supported_mask = mask; + return !!mask; +} + +/*****************************************************************************/ +/* AT+CFUN? response parsers */ + +gboolean +mm_mbm_parse_cfun_query_power_state (const gchar *response, + MMModemPowerState *out_state, + GError **error) +{ + guint state; + + if (!mm_3gpp_parse_cfun_query_response (response, &state, error)) + return FALSE; + + switch (state) { + case MBM_NETWORK_MODE_OFFLINE: + *out_state = MM_MODEM_POWER_STATE_OFF; + return TRUE; + case MBM_NETWORK_MODE_LOW_POWER: + *out_state = MM_MODEM_POWER_STATE_LOW; + return TRUE; + case MBM_NETWORK_MODE_ANY: + case MBM_NETWORK_MODE_2G: + case MBM_NETWORK_MODE_3G: + *out_state = MM_MODEM_POWER_STATE_ON; + return TRUE; + default: + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown +CFUN pòwer state: '%u'", state); + return FALSE; + } +} + +gboolean +mm_mbm_parse_cfun_query_current_modes (const gchar *response, + MMModemMode *allowed, + gint *mbm_mode, + GError **error) +{ + guint state; + + g_assert (mbm_mode); + g_assert (allowed); + + if (!mm_3gpp_parse_cfun_query_response (response, &state, error)) + return FALSE; + + switch (state) { + case MBM_NETWORK_MODE_OFFLINE: + case MBM_NETWORK_MODE_LOW_POWER: + /* Do not update mbm_mode */ + *allowed = MM_MODEM_MODE_NONE; + return TRUE; + case MBM_NETWORK_MODE_2G: + *mbm_mode = MBM_NETWORK_MODE_2G; + *allowed = MM_MODEM_MODE_2G; + return TRUE; + case MBM_NETWORK_MODE_3G: + *mbm_mode = MBM_NETWORK_MODE_3G; + *allowed = MM_MODEM_MODE_3G; + return TRUE; + case MBM_NETWORK_MODE_ANY: + /* Do not update mbm_mode */ + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + return TRUE; + default: + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown +CFUN current mode: '%u'", state); + return FALSE; + } +} diff --git a/src/plugins/mbm/mm-modem-helpers-mbm.h b/src/plugins/mbm/mm-modem-helpers-mbm.h new file mode 100644 index 00000000..3e3bf57a --- /dev/null +++ b/src/plugins/mbm/mm-modem-helpers-mbm.h @@ -0,0 +1,51 @@ +/* -*- 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) 2014 Dan Williams <dcbw@redhat.com> + */ + +#ifndef MM_MODEM_HELPERS_MBM_H +#define MM_MODEM_HELPERS_MBM_H + +#include "glib.h" + +/* *E2IPCFG response parser */ +gboolean mm_mbm_parse_e2ipcfg_response (const gchar *response, + MMBearerIpConfig **out_ip4_config, + MMBearerIpConfig **out_ip6_config, + GError **error); + +typedef enum { + MBM_NETWORK_MODE_OFFLINE = 0, + MBM_NETWORK_MODE_ANY = 1, + MBM_NETWORK_MODE_LOW_POWER = 4, + MBM_NETWORK_MODE_2G = 5, + MBM_NETWORK_MODE_3G = 6, +} MbmNetworkMode; + +/* AT+CFUN=? test parser + * Returns a bitmask, bit index set for the supported modes reported */ +gboolean mm_mbm_parse_cfun_test (const gchar *response, + gpointer log_object, + guint32 *supported_mask, + GError **error); + +/* AT+CFUN? response parsers */ +gboolean mm_mbm_parse_cfun_query_power_state (const gchar *response, + MMModemPowerState *out_state, + GError **error); +gboolean mm_mbm_parse_cfun_query_current_modes (const gchar *response, + MMModemMode *allowed, + gint *mbm_mode, + GError **error); + +#endif /* MM_MODEM_HELPERS_MBM_H */ diff --git a/src/plugins/mbm/mm-plugin-mbm.c b/src/plugins/mbm/mm-plugin-mbm.c new file mode 100644 index 00000000..6790d8a8 --- /dev/null +++ b/src/plugins/mbm/mm-plugin-mbm.c @@ -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 Ericsson AB + * Copyright (C) 2012 Lanedo GmbH + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + * Author: Aleksander Morgado <aleksander@lanedo.com> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-mbm.h" +#include "mm-broadband-modem-mbm.h" + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#endif + +G_DEFINE_TYPE (MMPluginMbm, mm_plugin_mbm, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered Ericsson modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_mbm_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const gchar *udev_tags[] = { + "ID_MM_ERICSSON_MBM", + NULL + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_MBM, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + NULL)); +} + +static void +mm_plugin_mbm_init (MMPluginMbm *self) +{ +} + +static void +mm_plugin_mbm_class_init (MMPluginMbmClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/mbm/mm-plugin-mbm.h b/src/plugins/mbm/mm-plugin-mbm.h new file mode 100644 index 00000000..ac07d7e1 --- /dev/null +++ b/src/plugins/mbm/mm-plugin-mbm.h @@ -0,0 +1,43 @@ +/* -*- 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 Ericsson AB + * Copyright (C) 2012 Lanedo GmbH + * + * Author: Per Hallsmark <per.hallsmark@ericsson.com> + */ + +#ifndef MM_PLUGIN_MBM_H +#define MM_PLUGIN_MBM_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_MBM (mm_plugin_mbm_get_type ()) +#define MM_PLUGIN_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_MBM, MMPluginMbm)) +#define MM_PLUGIN_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_MBM, MMPluginMbmClass)) +#define MM_IS_PLUGIN_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_MBM)) +#define MM_IS_PLUGIN_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_MBM)) +#define MM_PLUGIN_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_MBM, MMPluginMbmClass)) + +typedef struct { + MMPlugin parent; +} MMPluginMbm; + +typedef struct { + MMPluginClass parent; +} MMPluginMbmClass; + +GType mm_plugin_mbm_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_MBM_H */ diff --git a/src/plugins/mbm/mm-sim-mbm.c b/src/plugins/mbm/mm-sim-mbm.c new file mode 100644 index 00000000..d3f73954 --- /dev/null +++ b/src/plugins/mbm/mm-sim-mbm.c @@ -0,0 +1,242 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-base-modem-at.h" +#include "mm-sim-mbm.h" + +G_DEFINE_TYPE (MMSimMbm, mm_sim_mbm, MM_TYPE_BASE_SIM) + +/*****************************************************************************/ +/* SEND PIN/PUK (Generic implementation) */ + +typedef struct { + MMBaseModem *modem; + guint retries; +} SendPinPukContext; + +static void +send_pin_puk_context_free (SendPinPukContext *ctx) +{ + g_object_unref (ctx->modem); + g_slice_free (SendPinPukContext, ctx); +} + +static gboolean +common_send_pin_puk_finish (MMBaseSim *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void wait_for_unlocked_status (GTask *task); + +static void +cpin_query_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + + const gchar *result; + + result = mm_base_modem_at_command_finish (modem, res, NULL); + if (result && strstr (result, "READY")) { + /* All done! */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Need to recheck */ + wait_for_unlocked_status (task); +} + +static gboolean +cpin_query_cb (GTask *task) +{ + SendPinPukContext *ctx; + + ctx = g_task_get_task_data (task); + mm_base_modem_at_command (ctx->modem, + "+CPIN?", + 20, + FALSE, + (GAsyncReadyCallback)cpin_query_ready, + task); + return G_SOURCE_REMOVE; +} + +static void +wait_for_unlocked_status (GTask *task) +{ + MMSimMbm *self; + SendPinPukContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* Oops... :/ */ + if (ctx->retries == 0) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "PIN was sent but modem didn't report unlocked"); + g_object_unref (task); + return; + } + + /* Check status */ + ctx->retries--; + mm_obj_dbg (self, "scheduling lock state check..."); + g_timeout_add_seconds (1, (GSourceFunc)cpin_query_cb, task); +} + +static void +send_pin_puk_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + SendPinPukContext *ctx; + GError *error = NULL; + + mm_base_modem_at_command_finish (modem, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* No explicit error sending the PIN/PUK, now check status until we have the + * expected lock status */ + ctx = g_task_get_task_data (task); + ctx->retries = 3; + wait_for_unlocked_status (task); +} + +static void +common_send_pin_puk (MMBaseSim *self, + const gchar *pin, + const gchar *puk, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SendPinPukContext *ctx; + GTask *task; + gchar *command; + + ctx = g_slice_new (SendPinPukContext); + g_object_get (self, + MM_BASE_SIM_MODEM, &ctx->modem, + NULL); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)send_pin_puk_context_free); + + command = (puk ? + g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin) : + g_strdup_printf ("+CPIN=\"%s\"", pin)); + mm_base_modem_at_command (ctx->modem, + command, + 3, + FALSE, + (GAsyncReadyCallback)send_pin_puk_ready, + task); + g_free (command); +} + +static void +send_puk (MMBaseSim *self, + const gchar *puk, + const gchar *new_pin, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_send_pin_puk (self, new_pin, puk, callback, user_data); +} + +static void +send_pin (MMBaseSim *self, + const gchar *pin, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_send_pin_puk (self, pin, NULL, callback, user_data); +} + +/*****************************************************************************/ + +MMBaseSim * +mm_sim_mbm_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *source; + GObject *sim; + + source = g_async_result_get_source_object (res); + sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!sim) + return NULL; + + /* Only export valid SIMs */ + mm_base_sim_export (MM_BASE_SIM (sim)); + + return MM_BASE_SIM (sim); +} + +void +mm_sim_mbm_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (MM_TYPE_SIM_MBM, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_SIM_MODEM, modem, + "active", TRUE, /* by default always active */ + NULL); +} + +static void +mm_sim_mbm_init (MMSimMbm *self) +{ +} + +static void +mm_sim_mbm_class_init (MMSimMbmClass *klass) +{ + MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass); + + base_sim_class->send_pin = send_pin; + base_sim_class->send_pin_finish = common_send_pin_puk_finish; + base_sim_class->send_puk = send_puk; + base_sim_class->send_puk_finish = common_send_pin_puk_finish; +} diff --git a/src/plugins/mbm/mm-sim-mbm.h b/src/plugins/mbm/mm-sim-mbm.h new file mode 100644 index 00000000..1d843242 --- /dev/null +++ b/src/plugins/mbm/mm-sim-mbm.h @@ -0,0 +1,51 @@ +/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_SIM_MBM_H +#define MM_SIM_MBM_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-sim.h" + +#define MM_TYPE_SIM_MBM (mm_sim_mbm_get_type ()) +#define MM_SIM_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_MBM, MMSimMbm)) +#define MM_SIM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_MBM, MMSimMbmClass)) +#define MM_IS_SIM_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_MBM)) +#define MM_IS_SIM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_MBM)) +#define MM_SIM_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_MBM, MMSimMbmClass)) + +typedef struct _MMSimMbm MMSimMbm; +typedef struct _MMSimMbmClass MMSimMbmClass; + +struct _MMSimMbm { + MMBaseSim parent; +}; + +struct _MMSimMbmClass { + MMBaseSimClass parent; +}; + +GType mm_sim_mbm_get_type (void); + +void mm_sim_mbm_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_sim_mbm_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_SIM_MBM_H */ diff --git a/src/plugins/mbm/tests/test-modem-helpers-mbm.c b/src/plugins/mbm/tests/test-modem-helpers-mbm.c new file mode 100644 index 00000000..4169140a --- /dev/null +++ b/src/plugins/mbm/tests/test-modem-helpers-mbm.c @@ -0,0 +1,268 @@ +/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org> + * Copyright (C) 2014 Dan Williams <dcbw@redhat.com> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" + +/*****************************************************************************/ +/* Test *E2IPCFG responses */ + +typedef struct { + const gchar *str; + + /* IPv4 */ + const gchar *ipv4_addr; + const gchar *ipv4_gw; + const gchar *ipv4_dns1; + const gchar *ipv4_dns2; + + /* IPv6 */ + const gchar *ipv6_addr; + const gchar *ipv6_dns1; + const gchar *ipv6_dns2; +} E2ipcfgTest; + +static const E2ipcfgTest tests[] = { + { "*E2IPCFG: (1,\"46.157.32.246\")(2,\"46.157.32.243\")(3,\"193.213.112.4\")(3,\"130.67.15.198\")\r\n", + "46.157.32.246", "46.157.32.243", "193.213.112.4", "130.67.15.198", + NULL, NULL }, + + { "*E2IPCFG: (1,\"fe80:0000:0000:0000:0000:0000:e537:1801\")(3,\"2001:4600:0004:0fff:0000:0000:0000:0054\")(3,\"2001:4600:0004:1fff:0000:0000:0000:0054\")\r\n", + NULL, NULL, NULL, NULL, + "fe80:0000:0000:0000:0000:0000:e537:1801", "2001:4600:0004:0fff:0000:0000:0000:0054", "2001:4600:0004:1fff:0000:0000:0000:0054" }, + + { "*E2IPCFG: (1,\"fe80:0000:0000:0000:0000:0027:b7fe:9401\")(3,\"fd00:976a:0000:0000:0000:0000:0000:0009\")\r\n", + NULL, NULL, NULL, NULL, + "fe80:0000:0000:0000:0000:0027:b7fe:9401", "fd00:976a:0000:0000:0000:0000:0000:0009", NULL }, + + { NULL } +}; + +static void +test_e2ipcfg (void) +{ + guint i; + + for (i = 0; tests[i].str; i++) { + gboolean success; + GError *error = NULL; + MMBearerIpConfig *ipv4 = NULL; + MMBearerIpConfig *ipv6 = NULL; + const gchar **dns; + guint dnslen; + + success = mm_mbm_parse_e2ipcfg_response (tests[i].str, &ipv4, &ipv6, &error); + g_assert_no_error (error); + g_assert (success); + + /* IPv4 */ + if (tests[i].ipv4_addr) { + g_assert (ipv4); + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv4), ==, MM_BEARER_IP_METHOD_STATIC); + g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv4), ==, tests[i].ipv4_addr); + g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv4), ==, 28); + g_assert_cmpstr (mm_bearer_ip_config_get_gateway (ipv4), ==, tests[i].ipv4_gw); + + dns = mm_bearer_ip_config_get_dns (ipv4); + g_assert (dns); + dnslen = g_strv_length ((gchar **) dns); + if (tests[i].ipv4_dns2 != NULL) + g_assert_cmpint (dnslen, ==, 2); + else + g_assert_cmpint (dnslen, ==, 1); + g_assert_cmpstr (dns[0], ==, tests[i].ipv4_dns1); + g_assert_cmpstr (dns[1], ==, tests[i].ipv4_dns2); + g_object_unref (ipv4); + } else + g_assert (ipv4 == NULL); + + /* IPv6 */ + if (tests[i].ipv6_addr) { + struct in6_addr a6; + g_assert (ipv6); + + g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv6), ==, tests[i].ipv6_addr); + g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv6), ==, 64); + + g_assert (inet_pton (AF_INET6, mm_bearer_ip_config_get_address (ipv6), &a6)); + if (IN6_IS_ADDR_LINKLOCAL (&a6)) + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_DHCP); + else + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_STATIC); + + dns = mm_bearer_ip_config_get_dns (ipv6); + g_assert (dns); + dnslen = g_strv_length ((gchar **) dns); + if (tests[i].ipv6_dns2 != NULL) + g_assert_cmpint (dnslen, ==, 2); + else + g_assert_cmpint (dnslen, ==, 1); + g_assert_cmpstr (dns[0], ==, tests[i].ipv6_dns1); + g_assert_cmpstr (dns[1], ==, tests[i].ipv6_dns2); + g_object_unref (ipv6); + } else + g_assert (ipv6 == NULL); + } +} + +/*****************************************************************************/ +/* Test +CFUN test responses */ + +#define MAX_MODES 32 + +typedef struct { + const gchar *str; + guint32 expected_mask; +} CfunTest; + +static const CfunTest cfun_tests[] = { + { + "+CFUN: (0,1,4-6),(1-0)\r\n", + ((1 << MBM_NETWORK_MODE_OFFLINE) | + (1 << MBM_NETWORK_MODE_ANY) | + (1 << MBM_NETWORK_MODE_LOW_POWER) | + (1 << MBM_NETWORK_MODE_2G) | + (1 << MBM_NETWORK_MODE_3G)) + }, + { + "+CFUN: (0,1,4-6)\r\n", + ((1 << MBM_NETWORK_MODE_OFFLINE) | + (1 << MBM_NETWORK_MODE_ANY) | + (1 << MBM_NETWORK_MODE_LOW_POWER) | + (1 << MBM_NETWORK_MODE_2G) | + (1 << MBM_NETWORK_MODE_3G)) + }, + { + "+CFUN: (0,1,4)\r\n", + ((1 << MBM_NETWORK_MODE_OFFLINE) | + (1 << MBM_NETWORK_MODE_ANY) | + (1 << MBM_NETWORK_MODE_LOW_POWER)) + }, + { + "+CFUN: (0,1)\r\n", + ((1 << MBM_NETWORK_MODE_OFFLINE) | + (1 << MBM_NETWORK_MODE_ANY)) + }, +}; + +static void +test_cfun_test (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (cfun_tests); i++) { + guint32 mask; + gboolean success; + GError *error = NULL; + + success = mm_mbm_parse_cfun_test (cfun_tests[i].str, NULL, &mask, &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (mask, ==, cfun_tests[i].expected_mask); + } +} + +/*****************************************************************************/ + +typedef struct { + const gchar *str; + MMModemPowerState state; +} CfunQueryPowerStateTest; + +static const CfunQueryPowerStateTest cfun_query_power_state_tests[] = { + { "+CFUN: 0", MM_MODEM_POWER_STATE_OFF }, + { "+CFUN: 1", MM_MODEM_POWER_STATE_ON }, + { "+CFUN: 4", MM_MODEM_POWER_STATE_LOW }, + { "+CFUN: 5", MM_MODEM_POWER_STATE_ON }, + { "+CFUN: 6", MM_MODEM_POWER_STATE_ON }, +}; + +static void +test_cfun_query_power_state (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (cfun_query_power_state_tests); i++) { + GError *error = NULL; + gboolean success; + MMModemPowerState state; + + success = mm_mbm_parse_cfun_query_power_state (cfun_query_power_state_tests[i].str, &state, &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (cfun_query_power_state_tests[i].state, ==, state); + } +} + +typedef struct { + const gchar *str; + MMModemMode allowed; + gint mbm_mode; +} CfunQueryCurrentModeTest; + +static const CfunQueryCurrentModeTest cfun_query_current_mode_tests[] = { + { "+CFUN: 0", MM_MODEM_MODE_NONE, -1 }, + { "+CFUN: 1", MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, -1 }, + { "+CFUN: 4", MM_MODEM_MODE_NONE, -1 }, + { "+CFUN: 5", MM_MODEM_MODE_2G, MBM_NETWORK_MODE_2G }, + { "+CFUN: 6", MM_MODEM_MODE_3G, MBM_NETWORK_MODE_3G }, +}; + +static void +test_cfun_query_current_modes (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (cfun_query_current_mode_tests); i++) { + GError *error = NULL; + gboolean success; + MMModemMode allowed = MM_MODEM_MODE_NONE; + gint mbm_mode = -1; + + success = mm_mbm_parse_cfun_query_current_modes (cfun_query_current_mode_tests[i].str, &allowed, &mbm_mode, &error); + g_assert_no_error (error); + g_assert (success); + g_assert_cmpuint (cfun_query_current_mode_tests[i].allowed, ==, allowed); + g_assert_cmpint (cfun_query_current_mode_tests[i].mbm_mode, ==, mbm_mode); + } +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/mbm/e2ipcfg", test_e2ipcfg); + g_test_add_func ("/MM/mbm/cfun/test", test_cfun_test); + g_test_add_func ("/MM/mbm/cfun/query/power-state", test_cfun_query_power_state); + g_test_add_func ("/MM/mbm/cfun/query/current-modes", test_cfun_query_current_modes); + + return g_test_run (); +} |