[PATCH] Plugin for Thuraya XT

Aleksander Morgado aleksander at aleksander.es
Sun Jan 31 13:44:52 PST 2016


Hey again,


On 31/01/16 06:23, Thomas Sailer wrote:
> The Thuraya XT (http://www.thuraya.com/thuraya-xt) is a satellite
> handset that can be connected to a computer using USB and that supports
> a 64kBit/s packet switched IPv4 internet access service called GmPRS.
> 
> Like most satellite modems, it more or less supports the AT command
> set, just enough to make older versions of Windows happy, but chokes on
> some commands, like AT+COPS=0, which causes it to loose the signal and
> behave strangely. But since there's only one possible operator, the
> whole operator switching can safely be omitted. Hence the need for a
> separate Thuraya plugin.
> 

Thanks for the debug logs; they look mostly good.

There are some issues with AT+CPMS response parsing, though. Could you
try to see if we can update the code to parse the responses properly?

More comments inline.

> Thomas
> 
> 
>  plugins/Makefile.am                          |  14 +
>  plugins/thuraya/mm-bearer-thuraya.c          | 787 +++++++++++++++++++++++++++
>  plugins/thuraya/mm-bearer-thuraya.h          |  61 +++
>  plugins/thuraya/mm-broadband-modem-thuraya.c | 389 +++++++++++++
>  plugins/thuraya/mm-broadband-modem-thuraya.h |  50 ++
>  plugins/thuraya/mm-plugin-thuraya.c          |  93 ++++
>  plugins/thuraya/mm-plugin-thuraya.h          |  49 ++
>  plugins/thuraya/mm-sim-thuraya.c             |  90 +++
>  plugins/thuraya/mm-sim-thuraya.h             |  54 ++
>  src/mm-modem-helpers.c                       |  13 +-
>  10 files changed, 1598 insertions(+), 2 deletions(-)
> 
> diff --git a/plugins/Makefile.am b/plugins/Makefile.am
> index 3cc5e2f..52b4de6 100644
> --- a/plugins/Makefile.am
> +++ b/plugins/Makefile.am
> @@ -125,6 +125,7 @@ pkglib_LTLIBRARIES = \
>  	libmm-plugin-nokia-icera.la \
>  	libmm-plugin-cinterion.la \
>  	libmm-plugin-iridium.la \
> +	libmm-plugin-thuraya.la \
>  	libmm-plugin-motorola.la \
>  	libmm-plugin-novatel.la \
>  	libmm-plugin-novatel-lte.la \
> @@ -461,6 +462,19 @@ libmm_plugin_iridium_la_SOURCES = \
>  libmm_plugin_iridium_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
>  libmm_plugin_iridium_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
>  
> +# Thuraya XT
> +libmm_plugin_thuraya_la_SOURCES = \
> +	thuraya/mm-plugin-thuraya.c \
> +	thuraya/mm-plugin-thuraya.h \
> +	thuraya/mm-broadband-modem-thuraya.c \
> +	thuraya/mm-broadband-modem-thuraya.h \
> +	thuraya/mm-bearer-thuraya.c \
> +	thuraya/mm-bearer-thuraya.h \
> +	thuraya/mm-sim-thuraya.c \
> +	thuraya/mm-sim-thuraya.h
> +libmm_plugin_thuraya_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
> +libmm_plugin_thuraya_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
> +
>  # Common Novatel modem support library
>  noinst_LTLIBRARIES += libmm-utils-novatel.la
>  libmm_utils_novatel_la_SOURCES = \
> diff --git a/plugins/thuraya/mm-bearer-thuraya.c b/plugins/thuraya/mm-bearer-thuraya.c
> new file mode 100644
> index 0000000..813fc84
> --- /dev/null
> +++ b/plugins/thuraya/mm-bearer-thuraya.c
> @@ -0,0 +1,787 @@
> +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
> +/*
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details:
> + *
> + * Copyright (C) 2008 - 2009 Novell, Inc.
> + * Copyright (C) 2009 - 2012 Red Hat, Inc.
> + * Copyright (C) 2011 - 2012 Google, Inc.
> + * Copyright (C) 2011 - 2013 Aleksander Morgado <aleksander at gnu.org>
> + * Copyright (C) 2016 Thomas Sailer <t.sailer at alumni.ethz.ch>
> + *
> + * modelled after mm-broadband-bearer.c
> + */
> +
> +#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-bearer-thuraya.h"
> +#include "mm-base-modem-at.h"
> +#include "mm-log.h"
> +
> +/* Allow up to 200s to get a proper IP connection */
> +#define BEARER_THURAYA_IP_TIMEOUT_DEFAULT 200
> +
> +G_DEFINE_TYPE (MMBearerThuraya, mm_bearer_thuraya, MM_TYPE_BASE_BEARER);
> +
> +struct _MMBearerThurayaPrivate {
> +    /*-- Common stuff --*/
> +    /* Data port used when modem is connected */
> +    MMPort *port;
> +};
> +
> +/*****************************************************************************/
> +/* Detailed connect context */
> +
> +typedef struct {
> +    MMBearerThuraya *self;
> +    MMBaseModem *modem;
> +    MMPortSerialAt *primary;
> +    MMPortSerialAt *secondary;
> +    GCancellable *cancellable;
> +    GSimpleAsyncResult *result;
> +
> +    MMPort *data;
> +    gboolean close_data_on_exit;
> +} DetailedConnectContext;
> +
> +static MMBearerConnectResult *
> +detailed_connect_finish (MMBearerThuraya *self,
> +                         GAsyncResult *res,
> +                         GError **error)
> +{
> +    if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
> +        return NULL;
> +
> +    return mm_bearer_connect_result_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)));
> +}
> +
> +static void
> +detailed_connect_context_complete_and_free (DetailedConnectContext *ctx)
> +{
> +    g_simple_async_result_complete_in_idle (ctx->result);
> +    g_object_unref (ctx->result);
> +    g_object_unref (ctx->cancellable);
> +    g_object_unref (ctx->primary);
> +    if (ctx->secondary)
> +        g_object_unref (ctx->secondary);
> +    if (ctx->data) {
> +        if (ctx->close_data_on_exit)
> +            mm_port_serial_close (MM_PORT_SERIAL (ctx->data));
> +        g_object_unref (ctx->data);
> +    }
> +    g_object_unref (ctx->self);
> +    g_object_unref (ctx->modem);
> +    g_slice_free (DetailedConnectContext, ctx);
> +}
> +
> +static DetailedConnectContext *
> +detailed_connect_context_new (MMBearerThuraya *self,
> +                              MMBroadbandModem *modem,
> +                              MMPortSerialAt *primary,
> +                              MMPortSerialAt *secondary,
> +                              GCancellable *cancellable,
> +                              GAsyncReadyCallback callback,
> +                              gpointer user_data)
> +{
> +    DetailedConnectContext *ctx;
> +
> +    ctx = g_slice_new0 (DetailedConnectContext);
> +    ctx->self = g_object_ref (self);
> +    ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
> +    ctx->primary = g_object_ref (primary);
> +    ctx->secondary = (secondary ? g_object_ref (secondary) : NULL);
> +    ctx->result = g_simple_async_result_new (G_OBJECT (self),
> +                                             callback,
> +                                             user_data,
> +                                             detailed_connect_context_new);
> +
> +    /* NOTE:
> +     * We don't currently support cancelling AT commands, so we'll just check
> +     * whether the operation is to be cancelled at each step. */
> +    ctx->cancellable = g_object_ref (cancellable);
> +    return ctx;
> +}
> +
> +/*****************************************************************************/
> +/* Generic implementations are always AT-port based */
> +
> +static MMPortSerialAt *
> +common_get_at_data_port (MMBaseModem *modem,
> +                         GError **error)
> +{
> +    MMPort *data;
> +
> +    /* Look for best data port, NULL if none available. */
> +    data = mm_base_modem_peek_best_data_port (modem, MM_PORT_TYPE_AT);
> +    if (!data) {
> +        /* It may happen that the desired data port grabbed during probing was
> +         * actually a 'net' port, which the generic logic cannot handle, so if
> +         * that is the case, and we have no AT data ports specified, just
> +         fallback to the primary AT port. */
> +        data = (MMPort *) mm_base_modem_peek_port_primary (modem);
> +    }
> +
> +    g_assert (MM_IS_PORT_SERIAL_AT (data));
> +
> +    if (!mm_port_serial_open (MM_PORT_SERIAL (data), error)) {
> +        g_prefix_error (error, "Couldn't connect: cannot keep data port open.");
> +        return NULL;
> +    }
> +
> +    mm_dbg ("Connection through a plain serial AT port (%s)", mm_port_get_device (data));
> +
> +    return MM_PORT_SERIAL_AT (g_object_ref (data));
> +}
> +
> +/*****************************************************************************/
> +/* CONNECT Thuraya
> + *
> + * Thuraya connection procedure of a bearer involves several steps:
> + * 1) Get data port from the modem. Default implementation will have only
> + *    one single possible data port, but plugins may have more.
> + * 2) Initiate call.
> + */
> +
> +static void
> +dial_ready (MMBaseModem *modem,
> +            GAsyncResult *res,
> +            DetailedConnectContext *ctx)
> +{
> +    GError *error = NULL;
> +    MMBearerIpConfig *config;
> +
> +    /* DO NOT check for cancellable here. If we got here without errors, the
> +     * bearer is really connected and therefore we need to reflect that in
> +     * the state machine. */
> +    mm_base_modem_at_command_full_finish (modem, res, &error);
> +    if (error) {
> +        mm_warn ("Couldn't connect: '%s'", error->message);
> +        g_simple_async_result_take_error (ctx->result, error);
> +        detailed_connect_context_complete_and_free (ctx);
> +        return;
> +    }
> +
> +    /* else... Yuhu! */
> +
> +    /* Keep port open during connection */
> +    ctx->close_data_on_exit = FALSE;
> +
> +    /* Generic CDMA connections are done over PPP always */
> +    g_assert (MM_IS_PORT_SERIAL_AT (ctx->data));
> +    config = mm_bearer_ip_config_new ();
> +    mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_PPP);
> +
> +    /* Assume only IPv4 is given */
> +    g_simple_async_result_set_op_res_gpointer (
> +        ctx->result,
> +        mm_bearer_connect_result_new (ctx->data, config, NULL),
> +        (GDestroyNotify)mm_bearer_connect_result_unref);
> +    detailed_connect_context_complete_and_free (ctx);
> +
> +    g_object_unref (config);
> +}
> +
> +static void
> +apn_ready (MMBaseModem *modem,
> +            GAsyncResult *res,
> +            DetailedConnectContext *ctx)
> +{
> +    GError *error = NULL;
> +    gchar *command;
> +    const gchar *number;
> +
> +    mm_base_modem_at_command_full_finish (modem, res, &error);
> +    if (error) {
> +        mm_warn ("Couldn't connect: '%s'", error->message);
> +        g_simple_async_result_take_error (ctx->result, error);
> +        detailed_connect_context_complete_and_free (ctx);
> +        return;
> +    }
> +
> +    /* start dialling */
> +    number = mm_bearer_properties_get_number (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)));
> +
> +    /* If a number was given when creating the bearer, use that one.
> +     * Otherwise, use the default one, *99#
> +     */
> +    if (number)
> +        command = g_strconcat ("DT", number, NULL);
> +    else
> +        command = g_strdup ("DT*99#");
> +
> +    mm_base_modem_at_command_full (ctx->modem,
> +                                   MM_PORT_SERIAL_AT (ctx->data),
> +                                   command,
> +                                   90,
> +                                   FALSE,
> +                                   FALSE,
> +                                   NULL,
> +                                   (GAsyncReadyCallback)dial_ready,
> +                                   ctx);
> +    g_free (command);
> +}
> +
> +static void
> +connect_thuraya (MMBearerThuraya *self,
> +                 MMBroadbandModem *modem,
> +                 MMPortSerialAt *primary,
> +                 MMPortSerialAt *secondary, /* unused by us */
> +                 GCancellable *cancellable,
> +                 GAsyncReadyCallback callback,
> +                 gpointer user_data)
> +{
> +    DetailedConnectContext *ctx;
> +    GError *error = NULL;
> +
> +    g_assert (primary != NULL);
> +
> +    ctx = detailed_connect_context_new (self,
> +                                        modem,
> +                                        primary,
> +                                        NULL,
> +                                        cancellable,
> +                                        callback,
> +                                        user_data);
> +
> +    /* Grab dial port. This gets a reference to the dial port and OPENs it.
> +     * If we fail, we'll need to close it ourselves. */
> +    ctx->data = (MMPort *)common_get_at_data_port (ctx->modem, &error);
> +    if (!ctx->data) {
> +        g_simple_async_result_take_error (ctx->result, error);
> +        detailed_connect_context_complete_and_free (ctx);
> +        return;
> +    }
> +    ctx->close_data_on_exit = TRUE;
> +
> +    /* need to set the APN even though the phone already has it */
> +    mm_base_modem_at_command_full (ctx->modem,
> +                                   MM_PORT_SERIAL_AT (ctx->data),
> +                                   "+CGDCONT=1,\"IP\",\"get\"",
> +                                   90,
> +                                   FALSE,
> +                                   FALSE,
> +                                   NULL,
> +                                   (GAsyncReadyCallback)apn_ready,
> +                                   ctx);
> +}
> +
> +/*****************************************************************************/
> +/* CONNECT */
> +
> +typedef struct {
> +    MMBearerThuraya *self;
> +    GSimpleAsyncResult *result;
> +} ConnectContext;
> +
> +static void
> +connect_context_complete_and_free (ConnectContext *ctx)
> +{
> +    g_simple_async_result_complete_in_idle (ctx->result);
> +    g_object_unref (ctx->result);
> +    g_object_unref (ctx->self);
> +    g_slice_free (ConnectContext, ctx);
> +}
> +
> +static MMBearerConnectResult *
> +connect_finish (MMBaseBearer *self,
> +                GAsyncResult *res,
> +                GError **error)
> +{
> +    if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
> +        return NULL;
> +
> +    return mm_bearer_connect_result_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)));
> +}
> +
> +static void
> +connect_succeeded (ConnectContext *ctx,
> +                   MMBearerConnectResult *result)
> +{
> +    /* Keep connected port and type of connection */
> +    ctx->self->priv->port = g_object_ref (mm_bearer_connect_result_peek_data (result));
> +
> +    /* Port is connected; update the state */
> +    mm_port_set_connected (ctx->self->priv->port, TRUE);
> +
> +    /* Set operation result */
> +    g_simple_async_result_set_op_res_gpointer (ctx->result,
> +                                               result,
> +                                               (GDestroyNotify)mm_bearer_connect_result_unref);
> +    connect_context_complete_and_free (ctx);
> +}
> +
> +static void
> +connect_ready (MMBearerThuraya *self,
> +                    GAsyncResult *res,
> +                    ConnectContext *ctx)
> +{
> +    MMBearerConnectResult *result;
> +    GError *error = NULL;
> +
> +    result = detailed_connect_finish (self, res, &error);
> +    if (!result) {
> +        g_simple_async_result_take_error (ctx->result, error);
> +        connect_context_complete_and_free (ctx);
> +        return;
> +    }
> +
> +    /* take result */
> +    connect_succeeded (ctx, result);
> +}
> +
> +static void
> +connect (MMBaseBearer *self,
> +         GCancellable *cancellable,
> +         GAsyncReadyCallback callback,
> +         gpointer user_data)
> +{
> +    MMBaseModem *modem = NULL;
> +    MMPortSerialAt *primary;
> +    ConnectContext *ctx;
> +
> +    /* Don't try to connect if already connected */
> +    if (MM_BEARER_THURAYA (self)->priv->port) {
> +        g_simple_async_report_error_in_idle (
> +            G_OBJECT (self),
> +            callback,
> +            user_data,
> +            MM_CORE_ERROR,
> +            MM_CORE_ERROR_CONNECTED,
> +            "Couldn't connect: this bearer is already connected");
> +        return;
> +    }
> +
> +    /* Get the owner modem object */
> +    g_object_get (self,
> +                  MM_BASE_BEARER_MODEM, &modem,
> +                  NULL);
> +    g_assert (modem != NULL);
> +
> +    /* We will launch the ATD call in the primary port... */
> +    primary = mm_base_modem_peek_port_primary (modem);
> +    if (!primary) {
> +        g_simple_async_report_error_in_idle (
> +            G_OBJECT (self),
> +            callback,
> +            user_data,
> +            MM_CORE_ERROR,
> +            MM_CORE_ERROR_CONNECTED,
> +            "Couldn't connect: couldn't get primary port");
> +        g_object_unref (modem);
> +        return;
> +    }
> +
> +    /* ...only if not already connected */
> +    if (mm_port_get_connected (MM_PORT (primary))) {
> +        g_simple_async_report_error_in_idle (
> +            G_OBJECT (self),
> +            callback,
> +            user_data,
> +            MM_CORE_ERROR,
> +            MM_CORE_ERROR_CONNECTED,
> +            "Couldn't connect: primary AT port is already connected");
> +        g_object_unref (modem);
> +        return;
> +    }
> +
> +    /* In this context, we only keep the stuff we'll need later */
> +    ctx = g_slice_new0 (ConnectContext);
> +    ctx->self = g_object_ref (self);
> +    ctx->result = g_simple_async_result_new (G_OBJECT (self),
> +                                             callback,
> +                                             user_data,
> +                                             connect);
> +
> +    mm_dbg ("Launching Thuraya connection attempt");
> +    connect_thuraya (
> +        MM_BEARER_THURAYA (self),
> +        MM_BROADBAND_MODEM (modem),
> +        primary,
> +        mm_base_modem_peek_port_secondary (modem),
> +        cancellable,
> +        (GAsyncReadyCallback) connect_ready,
> +        ctx);
> +    g_object_unref (modem);
> +}
> +
> +/*****************************************************************************/
> +/* Detailed disconnect context */
> +
> +typedef struct {
> +    MMBearerThuraya *self;
> +    MMBaseModem *modem;
> +    MMPortSerialAt *primary;
> +    MMPortSerialAt *secondary;
> +    MMPort *data;
> +    GSimpleAsyncResult *result;
> +} DetailedDisconnectContext;
> +
> +static gboolean
> +detailed_disconnect_finish (MMBearerThuraya *self,
> +                            GAsyncResult *res,
> +                            GError **error)
> +{
> +    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
> +}
> +
> +static void
> +detailed_disconnect_context_complete_and_free (DetailedDisconnectContext *ctx)
> +{
> +    g_simple_async_result_complete_in_idle (ctx->result);
> +    g_object_unref (ctx->result);
> +    g_object_unref (ctx->data);
> +    g_object_unref (ctx->primary);
> +    if (ctx->secondary)
> +        g_object_unref (ctx->secondary);
> +    g_object_unref (ctx->self);
> +    g_object_unref (ctx->modem);
> +    g_free (ctx);
> +}
> +
> +static DetailedDisconnectContext *
> +detailed_disconnect_context_new (MMBearerThuraya *self,
> +                                 MMBroadbandModem *modem,
> +                                 MMPortSerialAt *primary,
> +                                 MMPortSerialAt *secondary,
> +                                 MMPort *data,
> +                                 GAsyncReadyCallback callback,
> +                                 gpointer user_data)
> +{
> +    DetailedDisconnectContext *ctx;
> +
> +    ctx = g_new0 (DetailedDisconnectContext, 1);
> +    ctx->self = g_object_ref (self);
> +    ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
> +    ctx->primary = g_object_ref (primary);
> +    ctx->secondary = (secondary ? g_object_ref (secondary) : NULL);
> +    ctx->data = g_object_ref (data);
> +    ctx->result = g_simple_async_result_new (G_OBJECT (self),
> +                                             callback,
> +                                             user_data,
> +                                             detailed_disconnect_context_new);
> +    return ctx;
> +}
> +
> +/*****************************************************************************/
> +/* Thuraya disconnect */
> +
> +static void
> +data_flash_ready (MMPortSerial *data,
> +                  GAsyncResult *res,
> +                  DetailedDisconnectContext *ctx)
> +{
> +    GError *error = NULL;
> +
> +    mm_port_serial_flash_finish (data, res, &error);
> +
> +    /* We kept the serial port open during connection, now we close that open
> +     * count */
> +    mm_port_serial_close (data);
> +
> +    /* Port is disconnected; update the state */
> +    mm_port_set_connected (MM_PORT (data), FALSE);
> +
> +    if (error) {
> +        /* Ignore "NO CARRIER" response when modem disconnects and any flash
> +         * failures we might encounter. Other errors are hard errors.
> +         */
> +        if (!g_error_matches (error,
> +                              MM_CONNECTION_ERROR,
> +                              MM_CONNECTION_ERROR_NO_CARRIER) &&
> +            !g_error_matches (error,
> +                              MM_SERIAL_ERROR,
> +                              MM_SERIAL_ERROR_FLASH_FAILED)) {
> +            /* Fatal */
> +            g_simple_async_result_take_error (ctx->result, error);
> +            detailed_disconnect_context_complete_and_free (ctx);
> +            return;
> +        }
> +
> +        mm_dbg ("Port flashing failed (not fatal): %s", error->message);
> +        g_error_free (error);
> +    }
> +
> +    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
> +    detailed_disconnect_context_complete_and_free (ctx);
> +}
> +
> +static void
> +data_reopen_ready (MMPortSerial *data,
> +                   GAsyncResult *res,
> +                   DetailedDisconnectContext *ctx)
> +{
> +    GError *error = NULL;
> +
> +    if (!mm_port_serial_reopen_finish (data, res, &error)) {
> +        /* Fatal */
> +        g_simple_async_result_take_error (ctx->result, error);
> +        detailed_disconnect_context_complete_and_free (ctx);
> +        return;
> +    }
> +
> +    /* Just flash the data port */
> +    mm_dbg ("Flashing data port (%s)...", mm_port_get_device (MM_PORT (ctx->data)));
> +    mm_port_serial_flash (MM_PORT_SERIAL (ctx->data),
> +                          1000,
> +                          TRUE,
> +                          (GAsyncReadyCallback)data_flash_ready,
> +                          ctx);
> +}
> +
> +static void
> +disconnect_thuraya (MMBearerThuraya *self,
> +                    MMBroadbandModem *modem,
> +                    MMPortSerialAt *primary,
> +                    MMPortSerialAt *secondary,
> +                    MMPort *data,
> +                    GAsyncReadyCallback callback,
> +                    gpointer user_data)
> +{
> +    DetailedDisconnectContext *ctx;
> +
> +    g_assert (primary != NULL);
> +
> +    /* Generic Thuraya plays only with SERIAL data ports */
> +    g_assert (MM_IS_PORT_SERIAL (data));
> +
> +    ctx = detailed_disconnect_context_new (self,
> +                                           modem,
> +                                           primary,
> +                                           secondary,
> +                                           data,
> +                                           callback,
> +                                           user_data);
> +
> +    /* Fully reopen the port before flashing */
> +    mm_dbg ("Reopening data port (%s)...", mm_port_get_device (MM_PORT (ctx->data)));
> +    mm_port_serial_reopen (MM_PORT_SERIAL (ctx->data),
> +                           1000,
> +                           (GAsyncReadyCallback)data_reopen_ready,
> +                           ctx);
> +}
> +
> +/*****************************************************************************/
> +/* DISCONNECT */
> +
> +typedef struct {
> +    MMBearerThuraya *self;
> +    GSimpleAsyncResult *result;
> +    MMPort *data;
> +} DisconnectContext;
> +
> +static void
> +disconnect_context_complete_and_free (DisconnectContext *ctx)
> +{
> +    g_simple_async_result_complete_in_idle (ctx->result);
> +    g_object_unref (ctx->result);
> +    g_object_unref (ctx->data);
> +    g_object_unref (ctx->self);
> +    g_free (ctx);
> +}
> +
> +static gboolean
> +disconnect_finish (MMBaseBearer *self,
> +                   GAsyncResult *res,
> +                   GError **error)
> +{
> +    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
> +}
> +
> +static void
> +reset_bearer_connection (MMBearerThuraya *self)
> +{
> +    if (self->priv->port) {
> +        /* Port is disconnected; update the state. Note: implementations may
> +         * already have set the port as disconnected (e.g the 3GPP one) */
> +        mm_port_set_connected (self->priv->port, FALSE);
> +
> +        /* Clear data port */
> +        g_clear_object (&self->priv->port);
> +    }
> +}
> +
> +static void
> +disconnect_succeeded (DisconnectContext *ctx)
> +{
> +    /* Cleanup all connection related data */
> +    reset_bearer_connection (ctx->self);
> +
> +    /* Set operation result */
> +    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
> +    disconnect_context_complete_and_free (ctx);
> +}
> +
> +static void
> +disconnect_failed (DisconnectContext *ctx,
> +                   GError *error)
> +{
> +    g_simple_async_result_take_error (ctx->result, error);
> +    disconnect_context_complete_and_free (ctx);
> +}
> +
> +static void
> +disconnect_ready (MMBearerThuraya *self,
> +                  GAsyncResult *res,
> +                  DisconnectContext *ctx)
> +{
> +    GError *error = NULL;
> +
> +    if (!detailed_disconnect_finish (self,
> +                                     res,
> +                                     &error))
> +        disconnect_failed (ctx, error);
> +    else
> +        disconnect_succeeded (ctx);
> +}
> +
> +static void
> +disconnect (MMBaseBearer *self,
> +            GAsyncReadyCallback callback,
> +            gpointer user_data)
> +{
> +    MMPortSerialAt *primary;
> +    MMBaseModem *modem = NULL;
> +    DisconnectContext *ctx;
> +
> +    if (!MM_BEARER_THURAYA (self)->priv->port) {
> +        g_simple_async_report_error_in_idle (
> +            G_OBJECT (self),
> +            callback,
> +            user_data,
> +            MM_CORE_ERROR,
> +            MM_CORE_ERROR_FAILED,
> +            "Couldn't disconnect: this bearer is not connected");
> +        return;
> +    }
> +
> +    g_object_get (self,
> +                  MM_BASE_BEARER_MODEM, &modem,
> +                  NULL);
> +    g_assert (modem != NULL);
> +
> +    /* We need the primary port to disconnect... */
> +    primary = mm_base_modem_peek_port_primary (modem);
> +    if (!primary) {
> +        g_simple_async_report_error_in_idle (
> +            G_OBJECT (self),
> +            callback,
> +            user_data,
> +            MM_CORE_ERROR,
> +            MM_CORE_ERROR_FAILED,
> +            "Couldn't disconnect: couldn't get primary port");
> +        g_object_unref (modem);
> +        return;
> +    }
> +
> +    /* In this context, we only keep the stuff we'll need later */
> +    ctx = g_new0 (DisconnectContext, 1);
> +    ctx->self = g_object_ref (self);
> +    ctx->data = g_object_ref (MM_BEARER_THURAYA (self)->priv->port);
> +    ctx->result = g_simple_async_result_new (G_OBJECT (self),
> +                                             callback,
> +                                             user_data,
> +                                             disconnect);
> +
> +    disconnect_thuraya (
> +        MM_BEARER_THURAYA (self),
> +        MM_BROADBAND_MODEM (modem),
> +        primary,
> +        mm_base_modem_peek_port_secondary (modem),
> +        MM_BEARER_THURAYA (self)->priv->port,
> +        (GAsyncReadyCallback) disconnect_ready,
> +        ctx);
> +
> +    g_object_unref (modem);
> +}
> +
> +/*****************************************************************************/
> +
> +static void
> +report_connection_status (MMBaseBearer *self,
> +                          MMBearerConnectionStatus status)
> +{
> +    if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED)
> +        /* Cleanup all connection related data */
> +        reset_bearer_connection (MM_BEARER_THURAYA (self));
> +
> +    /* Chain up parent's report_connection_status() */
> +    MM_BASE_BEARER_CLASS (mm_bearer_thuraya_parent_class)->report_connection_status (
> +        self,
> +        status);
> +}
> +
> +/*****************************************************************************/
> +
> +MMBaseBearer *
> +mm_bearer_thuraya_new (MMBroadbandModemThuraya *modem,
> +                       MMBearerProperties *config)
> +{
> +    MMBaseBearer *bearer;
> +
> +    /* The Thuraya bearer inherits from MMBaseBearer (so it's not a MMBearerThuraya)
> +     * and that means that the object is not async-initable, so we just use
> +     * g_object_get() here */
> +    bearer = g_object_new (MM_TYPE_BEARER_THURAYA,
> +                           MM_BASE_BEARER_MODEM, modem,
> +                           MM_BASE_BEARER_CONFIG, config,
> +                           "ip-timeout", BEARER_THURAYA_IP_TIMEOUT_DEFAULT,
> +                           NULL);
> +
> +    /* Only export valid bearers */
> +    mm_base_bearer_export (bearer);
> +
> +    return bearer;
> +}
> +
> +static void
> +mm_bearer_thuraya_init (MMBearerThuraya *self)
> +{
> +    /* Initialize private data */
> +    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
> +                                              MM_TYPE_BEARER_THURAYA,
> +                                              MMBearerThurayaPrivate);
> +}
> +
> +static void
> +dispose (GObject *object)
> +{
> +    MMBearerThuraya *self = MM_BEARER_THURAYA (object);
> +
> +    reset_bearer_connection (self);
> +
> +    G_OBJECT_CLASS (mm_bearer_thuraya_parent_class)->dispose (object);
> +}
> +
> +static void
> +mm_bearer_thuraya_class_init (MMBearerThurayaClass *klass)
> +{
> +    GObjectClass *object_class = G_OBJECT_CLASS (klass);
> +    MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
> +
> +    g_type_class_add_private (object_class, sizeof (MMBearerThurayaPrivate));
> +
> +    /* Virtual methods */
> +    object_class->dispose = dispose;
> +
> +    base_bearer_class->connect = connect;
> +    base_bearer_class->connect_finish = connect_finish;
> +    base_bearer_class->disconnect = disconnect;
> +    base_bearer_class->disconnect_finish = disconnect_finish;
> +    base_bearer_class->report_connection_status = report_connection_status;
> +}


I see the MMBearerThuraya is really replicating mostly the steps that
MMBroabandBearer is doing. I think this can be avoided, by making the
MMBearerThuraya inherit from MMBroabandBearer, and then implementing the
disconnect_3gpp() and connect_3gpp() methods in the subclass as needed.
Could you check if that's possible? We don't want to have the logic
duplicated in multiple places, as that would make it very hard to maintain.


> diff --git a/plugins/thuraya/mm-bearer-thuraya.h b/plugins/thuraya/mm-bearer-thuraya.h
> new file mode 100644
> index 0000000..6d9388c
> --- /dev/null
> +++ b/plugins/thuraya/mm-bearer-thuraya.h
> @@ -0,0 +1,61 @@
> +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
> +/*
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details:
> + *
> + * Copyright (C) 2008 - 2009 Novell, Inc.
> + * Copyright (C) 2009 - 2012 Red Hat, Inc.
> + * Copyright (C) 2011 - 2012 Google, Inc.
> + * Copyright (C) 2011 - 2013 Aleksander Morgado <aleksander at gnu.org>
> + * Copyright (C) 2016 Thomas Sailer <t.sailer at alumni.ethz.ch>
> + *
> + * modelled after mm-broadband-bearer.h
> + */
> +
> +#ifndef MM_BEARER_THURAYA_H
> +#define MM_BEARER_THURAYA_H
> +
> +#include <glib.h>
> +#include <glib-object.h>
> +
> +#define _LIBMM_INSIDE_MM
> +#include <libmm-glib.h>
> +
> +#include "mm-base-bearer.h"
> +#include "mm-broadband-modem-thuraya.h"
> +
> +#define MM_TYPE_BEARER_THURAYA            (mm_bearer_thuraya_get_type ())
> +#define MM_BEARER_THURAYA(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BEARER_THURAYA, MMBearerThuraya))
> +#define MM_BEARER_THURAYA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_BEARER_THURAYA, MMBearerThurayaClass))
> +#define MM_IS_BEARER_THURAYA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BEARER_THURAYA))
> +#define MM_IS_BEARER_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_BEARER_THURAYA))
> +#define MM_BEARER_THURAYA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_BEARER_THURAYA, MMBearerThurayaClass))
> +
> +typedef struct _MMBearerThuraya MMBearerThuraya;
> +typedef struct _MMBearerThurayaClass MMBearerThurayaClass;
> +typedef struct _MMBearerThurayaPrivate MMBearerThurayaPrivate;
> +
> +struct _MMBearerThuraya {
> +    MMBaseBearer parent;
> +    MMBearerThurayaPrivate *priv;
> +};
> +
> +struct _MMBearerThurayaClass {
> +    MMBaseBearerClass parent;
> +};
> +
> +GType mm_bearer_thuraya_get_type (void);
> +
> +/* Thuraya bearer creation implementation.
> + * NOTE it is *not* a broadband bearer, so not async-initable */
> +MMBaseBearer *mm_bearer_thuraya_new (MMBroadbandModemThuraya *modem,
> +                                     MMBearerProperties *properties);
> +
> +#endif /* MM_BEARER_THURAYA_H */
> diff --git a/plugins/thuraya/mm-broadband-modem-thuraya.c b/plugins/thuraya/mm-broadband-modem-thuraya.c
> new file mode 100644
> index 0000000..f5b657e
> --- /dev/null
> +++ b/plugins/thuraya/mm-broadband-modem-thuraya.c
> @@ -0,0 +1,389 @@
> +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
> +/*
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details:
> + *
> + * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH
> + * Author: Aleksander Morgado <aleksander at lanedo.com>
> + * Copyright (C) 2016
> + * Author: Thomas Sailer <t.sailer at alumni.ethz.ch>
> + */
> +
> +#include <config.h>
> +
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <ctype.h>
> +
> +#include "ModemManager.h"
> +#include "mm-log.h"
> +#include "mm-errors-types.h"
> +#include "mm-base-modem-at.h"
> +#include "mm-iface-modem.h"
> +#include "mm-iface-modem-3gpp.h"
> +#include "mm-iface-modem-messaging.h"
> +#include "mm-broadband-modem-thuraya.h"
> +#include "mm-sim-thuraya.h"
> +#include "mm-bearer-thuraya.h"
> +#include "mm-modem-helpers.h"
> +
> +static void iface_modem_init (MMIfaceModem *iface);
> +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
> +static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
> +
> +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemThuraya, mm_broadband_modem_thuraya, 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_MESSAGING, iface_modem_messaging_init));
> +
> +/*****************************************************************************/
> +/* Operator Code and Name loading (3GPP interface) */
> +
> +static gchar *
> +load_operator_code_finish (MMIfaceModem3gpp *self,
> +                           GAsyncResult *res,
> +                           GError **error)
> +{
> +    /* Only "90103" operator code is assumed */
> +    return g_strdup ("90106");
> +}
> +
> +static gchar *
> +load_operator_name_finish (MMIfaceModem3gpp *self,
> +                           GAsyncResult *res,
> +                           GError **error)
> +{
> +    /* Only "THURAYA" operator name is assumed */
> +    return g_strdup ("THURAYA");
> +}
> +
> +static void
> +load_operator_name_or_code (MMIfaceModem3gpp *self,
> +                            GAsyncReadyCallback callback,
> +                            gpointer user_data)
> +{
> +    GSimpleAsyncResult *result;
> +
> +    result = g_simple_async_result_new (G_OBJECT (self),
> +                                        callback,
> +                                        user_data,
> +                                        load_operator_name_or_code);
> +    g_simple_async_result_set_op_res_gboolean (result, TRUE);
> +    g_simple_async_result_complete_in_idle (result);
> +    g_object_unref (result);
> +}
> +
> +/*****************************************************************************/
> +/* Enable unsolicited events (SMS indications) (Messaging interface) */
> +
> +static gboolean
> +messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self,
> +                                            GAsyncResult *res,
> +                                            GError **error)
> +{
> +    return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
> +}
> +
> +static void
> +messaging_enable_unsolicited_events (MMIfaceModemMessaging *self,
> +                                     GAsyncReadyCallback callback,
> +                                     gpointer user_data)
> +{
> +    /* AT+CNMI=<mode>,[<mt>[,<bm>[,<ds>[,<bfr>]]]]
> +     *  but <bm> can only be 0,
> +     *  and <ds> can only be either 0 or 1
> +     *
> +     * Note: Modem may return +CMS ERROR:322, which indicates Memory Full,
> +     * not a big deal
> +     */
> +    mm_base_modem_at_command (MM_BASE_MODEM (self),
> +                              "+CNMI=2,1,0,0,1",
> +                              3,
> +                              FALSE,
> +                              callback,
> +                              user_data);

I see the unsolicited messaging setup was taken from the Iridium plugin.
Does the Thuraya modem also require the same setup?

> +}
> +
> +/*****************************************************************************/
> +/* Flow control (Modem interface) */
> +
> +static gboolean
> +setup_flow_control_finish (MMIfaceModem *self,
> +                           GAsyncResult *res,
> +                           GError **error)
> +{
> +    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
> +}
> +
> +static void
> +setup_flow_control_ready (MMBroadbandModemThuraya *self,
> +                          GAsyncResult *res,
> +                          GSimpleAsyncResult *operation_result)
> +{
> +    GError *error = NULL;
> +
> +    if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
> +        /* Let the error be critical. We DO need RTS/CTS in order to have
> +         * proper modem disabling. */
> +        g_simple_async_result_take_error (operation_result, error);
> +    else
> +        g_simple_async_result_set_op_res_gboolean (operation_result, TRUE);
> +
> +    g_simple_async_result_complete (operation_result);
> +    g_object_unref (operation_result);
> +}
> +
> +static void
> +setup_flow_control (MMIfaceModem *self,
> +                    GAsyncReadyCallback callback,
> +                    gpointer user_data)
> +{
> +    GSimpleAsyncResult *result;
> +
> +    result = g_simple_async_result_new (G_OBJECT (self),
> +                                        callback,
> +                                        user_data,
> +                                        setup_flow_control);
> +
> +    /* Enable RTS/CTS flow control.
> +     * Other available values:
> +     *   AT&K0: Disable flow control
> +     *   AT&K3: RTS/CTS
> +     *   AT&K4: XOFF/XON
> +     *   AT&K6: Both RTS/CTS and XOFF/XON
> +     */
> +    mm_base_modem_at_command (MM_BASE_MODEM (self),
> +                              "&K3",
> +                              3,
> +                              FALSE,
> +                              (GAsyncReadyCallback)setup_flow_control_ready,
> +                              result);

I see the flow control logic was taken from the Iridium plugin. Does the
Thuraya modem also require this explicit setup? In Iridium it was
required because it was a plain RS232 port and otherwise it wouldn't
work properly.

> +}
> +
> +/*****************************************************************************/
> +/* Load supported modes (Modem inteface) */
> +
> +static GArray *
> +load_supported_modes_finish (MMIfaceModem *self,
> +                             GAsyncResult *res,
> +                             GError **error)
> +{
> +    return g_array_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)));
> +}
> +
> +static void
> +load_supported_modes (MMIfaceModem *self,
> +                      GAsyncReadyCallback callback,
> +                      gpointer user_data)
> +{
> +    GSimpleAsyncResult *result;
> +    GArray *combinations;
> +    MMModemModeCombination mode;
> +
> +    result = g_simple_async_result_new (G_OBJECT (self),
> +                                        callback,
> +                                        user_data,
> +                                        load_supported_modes);
> +
> +    /* Build list of combinations */
> +    combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
> +
> +    /* Report any, Thuraya connections are packet-switched */
> +    mode.allowed = MM_MODEM_MODE_ANY;
> +    mode.preferred = MM_MODEM_MODE_NONE;
> +    g_array_append_val (combinations, mode);
> +
> +    g_simple_async_result_set_op_res_gpointer (result, combinations, (GDestroyNotify) g_array_unref);
> +    g_simple_async_result_complete_in_idle (result);
> +    g_object_unref (result);
> +}

I think this could be part of the mm-iface-modem.c logic itself. In the
INITIALIZATION_STEP_SUPPORTED_MODES step, if load_supported_modes() and
load_supported_modes_finish() are NULL, then we could default to allowed
ANY and preferred NONE. Then, we can just provide NULL pointers, instead
of a custom method. What do you think?


> +
> +/*****************************************************************************/
> +/* Create SIM (Modem inteface) */
> +
> +static MMBaseSim *
> +create_sim_finish (MMIfaceModem *self,
> +                   GAsyncResult *res,
> +                   GError **error)
> +{
> +    return mm_sim_thuraya_new_finish (res, error);
> +}
> +
> +static void
> +create_sim (MMIfaceModem *self,
> +            GAsyncReadyCallback callback,
> +            gpointer user_data)
> +{
> +    /* New Thuraya SIM */
> +    mm_sim_thuraya_new (MM_BASE_MODEM (self),
> +                        NULL, /* cancellable */
> +                        callback,
> +                        user_data);
> +}
> +
> +/*****************************************************************************/
> +/* Create Bearer (Modem interface) */
> +
> +static MMBaseBearer *
> +create_bearer_finish (MMIfaceModem *self,
> +                      GAsyncResult *res,
> +                      GError **error)
> +{
> +    MMBaseBearer *bearer;
> +
> +    bearer = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
> +    mm_dbg ("New Thuraya bearer created at DBus path '%s'", mm_base_bearer_get_path (bearer));
> +
> +    return g_object_ref (bearer);
> +}
> +
> +static void
> +create_bearer (MMIfaceModem *self,
> +               MMBearerProperties *properties,
> +               GAsyncReadyCallback callback,
> +               gpointer user_data)
> +{
> +    MMBaseBearer *bearer;
> +    GSimpleAsyncResult *result;
> +
> +    result = g_simple_async_result_new (G_OBJECT (self),
> +                                        callback,
> +                                        user_data,
> +                                        create_bearer);
> +    mm_dbg ("Creating Thuraya bearer...");
> +    bearer = mm_bearer_thuraya_new (MM_BROADBAND_MODEM_THURAYA (self),
> +                                    properties);
> +    g_simple_async_result_set_op_res_gpointer (result,
> +                                               bearer,
> +                                               (GDestroyNotify)g_object_unref);
> +    g_simple_async_result_complete_in_idle (result);
> +    g_object_unref (result);
> +}
> +
> +/*****************************************************************************/
> +
> +static const gchar *primary_init_sequence[] = {
> +    /* Disable echo */
> +    "E0",
> +    /* Get word responses */
> +    "V1",
> +    /* Extended numeric codes */
> +    "+CMEE=1",
> +    NULL
> +};
> +

Looks like the init sequence above was taken from the Iridium plugin. Is
the default one in MMBroadbandModem not enough or behaving badly?

> +static void
> +setup_ports (MMBroadbandModem *self)
> +{
> +    MMPortSerialAt *primary;
> +
> +    /* Call parent's setup ports first always */
> +    MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_thuraya_parent_class)->setup_ports (self);
> +
> +    /* Set 9600 baudrate by default in the AT port */
> +    mm_dbg ("Baudrate will be set to 9600 bps...");
> +    primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
> +    if (!primary)
> +        return;
> +
> +    g_object_set (G_OBJECT (primary),
> +                  MM_PORT_SERIAL_BAUD, 9600,
> +                  MM_PORT_SERIAL_AT_INIT_SEQUENCE, primary_init_sequence,
> +                  NULL);
> +}
> +
> +/*****************************************************************************/
> +
> +MMBroadbandModemThuraya *
> +mm_broadband_modem_thuraya_new (const gchar *device,
> +                                const gchar **drivers,
> +                                const gchar *plugin,
> +                                guint16 vendor_id,
> +                                guint16 product_id)
> +{
> +    return g_object_new (MM_TYPE_BROADBAND_MODEM_THURAYA,
> +                         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,
> +                         /* Allow only up to 3 consecutive timeouts in the serial port */
> +                         MM_BASE_MODEM_MAX_TIMEOUTS, 3,

The previous setting was needed in the Iridium plugin because it was
hanging off a RS232 port, and we needed a way to detect that the modem
was gone. I don't think it's needed in this USB modem.

> +                         /* Only PS network is supported by the Thuraya modem */
> +                         MM_IFACE_MODEM_3GPP_CS_NETWORK_SUPPORTED, FALSE,

Is this so? Shouldn't it be CS-only, not PS-only?

> +                         NULL);
> +}
> +
> +static void
> +mm_broadband_modem_thuraya_init (MMBroadbandModemThuraya *self)
> +{
> +}
> +
> +static void
> +iface_modem_init (MMIfaceModem *iface)
> +{
> +    /* Create Thuraya-specific SIM and bearer*/
> +    iface->create_sim = create_sim;
> +    iface->create_sim_finish = create_sim_finish;
> +    iface->create_bearer = create_bearer;
> +    iface->create_bearer_finish = create_bearer_finish;
> +
> +    /* RTS/CTS flow control */
> +    iface->setup_flow_control = setup_flow_control;
> +    iface->setup_flow_control_finish = setup_flow_control_finish;
> +
> +    /* No need to power-up/power-down the modem */
> +    iface->load_power_state = NULL;
> +    iface->load_power_state_finish = NULL;
> +    iface->modem_power_up = NULL;
> +    iface->modem_power_up_finish = NULL;
> +    iface->modem_power_down = NULL;
> +    iface->modem_power_down_finish = NULL;
> +
> +    /* Supported modes cannot be queried */
> +    iface->load_supported_modes = load_supported_modes;
> +    iface->load_supported_modes_finish = load_supported_modes_finish;
> +}
> +
> +static void
> +iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
> +{
> +    /* Fixed operator code and name to be reported */
> +    iface->load_operator_name = load_operator_name_or_code;
> +    iface->load_operator_name_finish = load_operator_name_finish;
> +    iface->load_operator_code = load_operator_name_or_code;
> +    iface->load_operator_code_finish = load_operator_code_finish;
> +
> +    /* Don't try to scan networks with AT+COPS=?.
> +     * The Thuraya XT does not seem to properly support AT+COPS=?.
> +     * When issuing this command, it seems to get sufficiently confused
> +     * to drop the signal. Furthermore, it is useless anyway as there is only
> +     * one network supported, Thuraya.
> +     */
> +    iface->scan_networks = NULL;
> +    iface->scan_networks_finish = NULL;
> +}
> +
> +static void
> +iface_modem_messaging_init (MMIfaceModemMessaging *iface)
> +{
> +    iface->enable_unsolicited_events = messaging_enable_unsolicited_events;
> +    iface->enable_unsolicited_events_finish = messaging_enable_unsolicited_events_finish;
> +}
> +
> +static void
> +mm_broadband_modem_thuraya_class_init (MMBroadbandModemThurayaClass *klass)
> +{
> +    MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
> +
> +    broadband_modem_class->setup_ports = setup_ports;
> +}
> diff --git a/plugins/thuraya/mm-broadband-modem-thuraya.h b/plugins/thuraya/mm-broadband-modem-thuraya.h
> new file mode 100644
> index 0000000..42df9b9
> --- /dev/null
> +++ b/plugins/thuraya/mm-broadband-modem-thuraya.h
> @@ -0,0 +1,50 @@
> +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
> +/*
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details:
> + *
> + * Copyright (C) 2008 - 2009 Novell, Inc.
> + * Copyright (C) 2009 - 2011 Red Hat, Inc.
> + * Copyright (C) 2011 Google Inc.
> + * Copyright (C) 2016 Thomas Sailer <t.sailer at alumni.ethz.ch>
> + */
> +
> +#ifndef MM_BROADBAND_MODEM_THURAYA_H
> +#define MM_BROADBAND_MODEM_THURAYA_H
> +
> +#include "mm-broadband-modem.h"
> +
> +#define MM_TYPE_BROADBAND_MODEM_THURAYA            (mm_broadband_modem_thuraya_get_type ())
> +#define MM_BROADBAND_MODEM_THURAYA(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_THURAYA, MMBroadbandModemThuraya))
> +#define MM_BROADBAND_MODEM_THURAYA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_BROADBAND_MODEM_THURAYA, MMBroadbandModemThurayaClass))
> +#define MM_IS_BROADBAND_MODEM_THURAYA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_THURAYA))
> +#define MM_IS_BROADBAND_MODEM_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_BROADBAND_MODEM_THURAYA))
> +#define MM_BROADBAND_MODEM_THURAYA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_BROADBAND_MODEM_THURAYA, MMBroadbandModemThurayaClass))
> +
> +typedef struct _MMBroadbandModemThuraya MMBroadbandModemThuraya;
> +typedef struct _MMBroadbandModemThurayaClass MMBroadbandModemThurayaClass;
> +
> +struct _MMBroadbandModemThuraya {
> +    MMBroadbandModem parent;
> +};
> +
> +struct _MMBroadbandModemThurayaClass{
> +    MMBroadbandModemClass parent;
> +};
> +
> +GType mm_broadband_modem_thuraya_get_type (void);
> +
> +MMBroadbandModemThuraya *mm_broadband_modem_thuraya_new (const gchar *device,
> +                                                         const gchar **drivers,
> +                                                         const gchar *plugin,
> +                                                         guint16 vendor_id,
> +                                                         guint16 product_id);
> +
> +#endif /* MM_BROADBAND_MODEM_THURAYA_H */
> diff --git a/plugins/thuraya/mm-plugin-thuraya.c b/plugins/thuraya/mm-plugin-thuraya.c
> new file mode 100644
> index 0000000..3b5ca46
> --- /dev/null
> +++ b/plugins/thuraya/mm-plugin-thuraya.c
> @@ -0,0 +1,93 @@
> +/* -*- 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.
> + *
> + * You should have received a copy of the GNU General Public
> + * License along with this program; if not, write to the
> + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
> + * Boston, MA 02111-1307, USA.
> + *
> + * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH
> + * Author: Aleksander Morgado <aleksander at lanedo.com>
> + * Copyright (C) 2016
> + * Author: Thomas Sailer <t.sailer at alumni.ethz.ch>
> + */
> +
> +#include <string.h>
> +#include <gmodule.h>
> +
> +#define _LIBMM_INSIDE_MM
> +#include <libmm-glib.h>
> +
> +#include "mm-plugin-thuraya.h"
> +#include "mm-broadband-modem.h"
> +#include "mm-broadband-modem-thuraya.h"
> +#include "mm-private-boxed-types.h"
> +#include "mm-log.h"
> +
> +G_DEFINE_TYPE (MMPluginThuraya, mm_plugin_thuraya, MM_TYPE_PLUGIN)
> +
> +int mm_plugin_major_version = MM_PLUGIN_MAJOR_VERSION;
> +int mm_plugin_minor_version = MM_PLUGIN_MINOR_VERSION;
> +
> +static MMBaseModem *
> +create_modem (MMPlugin *self,
> +              const gchar *sysfs_path,
> +              const gchar **drivers,
> +              guint16 vendor,
> +              guint16 product,
> +              GList *probes,
> +              GError **error)
> +{
> +    return MM_BASE_MODEM (mm_broadband_modem_thuraya_new (sysfs_path,
> +                                                          drivers,
> +                                                          mm_plugin_get_name (self),
> +                                                          vendor,
> +                                                          product));
> +}
> +
> +/*****************************************************************************/
> +
> +G_MODULE_EXPORT MMPlugin *
> +mm_plugin_create (void)
> +{
> +    static const gchar *subsystems[] = { "tty", NULL };
> +    static const guint16 vendor_ids[] = { 0x1a26, 0 };
> +    static const guint16 product_ids[] = { 0x09cf, 0 };
> +    static const gchar *vendor_strings[] = { "manufacturer apsi", NULL };
> +    static const mm_str_pair product_strings[] = {{"manufacturer apsi", "thuraya xt" },
> +                                                  { NULL, NULL }};

The MM_PLUGIN_ALLOWED_VENDOR_STRINGS and
MM_PLUGIN_ALLOWED_PRODUCT_STRINGS properties should only be used if the
plugin is expecting pure RS232 devices. The device you've tested with is
a USB device, so it shoulnd't need those.

> +
> +    return MM_PLUGIN (
> +        g_object_new (MM_TYPE_PLUGIN_THURAYA,
> +                      MM_PLUGIN_NAME,                    "Thuraya",
> +                      MM_PLUGIN_ALLOWED_SUBSYSTEMS,      subsystems,
> +                      MM_PLUGIN_ALLOWED_VENDOR_STRINGS,  vendor_strings,
> +                      MM_PLUGIN_ALLOWED_PRODUCT_STRINGS, product_strings,
> +                      MM_PLUGIN_ALLOWED_VENDOR_IDS,      vendor_ids,
> +                      MM_PLUGIN_ALLOWED_PRODUCT_IDS,     product_ids,
> +                      MM_PLUGIN_ALLOWED_AT,              TRUE,
> +                      NULL));
> +}
> +
> +static void
> +mm_plugin_thuraya_init (MMPluginThuraya *self)
> +{
> +}
> +
> +static void
> +mm_plugin_thuraya_class_init (MMPluginThurayaClass *klass)
> +{
> +    MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
> +
> +    plugin_class->create_modem = create_modem;
> +}
> diff --git a/plugins/thuraya/mm-plugin-thuraya.h b/plugins/thuraya/mm-plugin-thuraya.h
> new file mode 100644
> index 0000000..d6ca2cd
> --- /dev/null
> +++ b/plugins/thuraya/mm-plugin-thuraya.h
> @@ -0,0 +1,49 @@
> +/* -*- 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.
> + *
> + * You should have received a copy of the GNU General Public
> + * License along with this program; if not, write to the
> + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
> + * Boston, MA 02111-1307, USA.
> + *
> + * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH
> + * Author: Aleksander Morgado <aleksander at lanedo.com>
> + * Copyright (C) 2016
> + * Author: Thomas Sailer <t.sailer at alumni.ethz.ch>
> + */
> +
> +#ifndef MM_PLUGIN_THURAYA_H
> +#define MM_PLUGIN_THURAYA_H
> +
> +#include "mm-plugin.h"
> +
> +#define MM_TYPE_PLUGIN_THURAYA            (mm_plugin_thuraya_get_type ())
> +#define MM_PLUGIN_THURAYA(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_THURAYA, MMPluginThuraya))
> +#define MM_PLUGIN_THURAYA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_PLUGIN_THURAYA, MMPluginThurayaClass))
> +#define MM_IS_PLUGIN_THURAYA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_THURAYA))
> +#define MM_IS_PLUGIN_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_PLUGIN_THURAYA))
> +#define MM_PLUGIN_THURAYA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_PLUGIN_THURAYA, MMPluginThurayaClass))
> +
> +typedef struct {
> +    MMPlugin parent;
> +} MMPluginThuraya;
> +
> +typedef struct {
> +    MMPluginClass parent;
> +} MMPluginThurayaClass;
> +
> +GType mm_plugin_thuraya_get_type (void);
> +
> +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
> +
> +#endif /* MM_PLUGIN_THURAYA_H */
> diff --git a/plugins/thuraya/mm-sim-thuraya.c b/plugins/thuraya/mm-sim-thuraya.c
> new file mode 100644
> index 0000000..4ed97d3
> --- /dev/null
> +++ b/plugins/thuraya/mm-sim-thuraya.c
> @@ -0,0 +1,90 @@
> +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
> +/*
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details:
> + *
> + * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH.
> + * Author: Aleksander Morgado <aleksander at lanedo.com>
> + * Copyright (C) 2016
> + * Author: Thomas Sailer <t.sailer at alumni.ethz.ch>
> + */
> +
> +#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-sim-thuraya.h"
> +
> +G_DEFINE_TYPE (MMSimThuraya, mm_sim_thuraya, MM_TYPE_BASE_SIM)
> +
> +/*****************************************************************************/
> +
> +MMBaseSim *
> +mm_sim_thuraya_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_thuraya_new (MMBaseModem *modem,
> +                    GCancellable *cancellable,
> +                    GAsyncReadyCallback callback,
> +                    gpointer user_data)
> +{
> +    g_async_initable_new_async (MM_TYPE_SIM_THURAYA,
> +                                G_PRIORITY_DEFAULT,
> +                                cancellable,
> +                                callback,
> +                                user_data,
> +                                MM_BASE_SIM_MODEM, modem,
> +                                NULL);
> +}
> +
> +static void
> +mm_sim_thuraya_init (MMSimThuraya *self)
> +{
> +}
> +
> +static void
> +mm_sim_thuraya_class_init (MMSimThurayaClass *klass)
> +{
> +    MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
> +
> +    /* Skip querying the SIM card info, not supported by Thuraya modems */
> +    base_sim_class->load_sim_identifier = NULL;
> +    base_sim_class->load_sim_identifier_finish = NULL;
> +    base_sim_class->load_imsi = NULL;
> +    base_sim_class->load_imsi_finish = NULL;
> +    base_sim_class->load_operator_identifier = NULL;
> +    base_sim_class->load_operator_identifier_finish = NULL;
> +    base_sim_class->load_operator_name = NULL;
> +    base_sim_class->load_operator_name_finish = NULL;
> +}
> diff --git a/plugins/thuraya/mm-sim-thuraya.h b/plugins/thuraya/mm-sim-thuraya.h
> new file mode 100644
> index 0000000..38e3237
> --- /dev/null
> +++ b/plugins/thuraya/mm-sim-thuraya.h
> @@ -0,0 +1,54 @@
> +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
> +/*
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details:
> + *
> + * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH.
> + * Author: Aleksander Morgado <aleksander at lanedo.com>
> + * Copyright (C) 2016
> + * Author: Thomas Sailer <t.sailer at alumni.ethz.ch>
> + */
> +
> +#ifndef MM_SIM_THURAYA_H
> +#define MM_SIM_THURAYA_H
> +
> +#include <glib.h>
> +#include <glib-object.h>
> +
> +#include "mm-base-sim.h"
> +
> +#define MM_TYPE_SIM_THURAYA            (mm_sim_thuraya_get_type ())
> +#define MM_SIM_THURAYA(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_THURAYA, MMSimThuraya))
> +#define MM_SIM_THURAYA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_SIM_THURAYA, MMSimThurayaClass))
> +#define MM_IS_SIM_THURAYA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_THURAYA))
> +#define MM_IS_SIM_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_SIM_THURAYA))
> +#define MM_SIM_THURAYA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_SIM_THURAYA, MMSimThurayaClass))
> +
> +typedef struct _MMSimThuraya MMSimThuraya;
> +typedef struct _MMSimThurayaClass MMSimThurayaClass;
> +
> +struct _MMSimThuraya {
> +    MMBaseSim parent;
> +};
> +
> +struct _MMSimThurayaClass {
> +    MMBaseSimClass parent;
> +};
> +
> +GType mm_sim_thuraya_get_type (void);
> +
> +void       mm_sim_thuraya_new        (MMBaseModem *modem,
> +                                      GCancellable *cancellable,
> +                                      GAsyncReadyCallback callback,
> +                                      gpointer user_data);
> +MMBaseSim *mm_sim_thuraya_new_finish (GAsyncResult  *res,
> +                                      GError       **error);
> +
> +#endif /* MM_SIM_THURAYA_H */
> diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
> index 17ad8d2..0835468 100644
> --- a/src/mm-modem-helpers.c
> +++ b/src/mm-modem-helpers.c
> @@ -434,6 +434,7 @@ mm_voice_clip_regex_get (void)
>  
>  /* +CREG: <stat>,<lac>,<ci>           (GSM 07.07 CREG=2 unsolicited) */
>  #define CREG3 "\\+(CREG|CGREG|CEREG):\\s*0*([0-9]),\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)"
> +#define CREG11 "\\+(CREG|CGREG|CEREG):\\s*0*([0-9]),\\s*(\"[^\"\\s]*\")\\s*,\\s*(\"[^\"\\s]*\")"
>  
>  /* +CREG: <n>,<stat>,<lac>,<ci>       (GSM 07.07 solicited and some CREG=2 unsolicited) */
>  #define CREG4 "\\+(CREG|CGREG|CEREG):\\s*([0-9]),\\s*([0-9])\\s*,\\s*([^,]*)\\s*,\\s*([^,\\s]*)"
> @@ -489,6 +490,14 @@ mm_3gpp_creg_regex_get (gboolean solicited)

Could you update the unit tests under /src/tests/test-modem-helpers.c to
consider the newly added regex matches?

Also, I think these changes, which affect the common helpers, could
deserve their own patch, explaining what was added/changed.

>      g_assert (regex);
>      g_ptr_array_add (array, regex);
>  
> +    /* #11 */
> +    if (solicited)
> +        regex = g_regex_new (CREG11 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
> +    else
> +        regex = g_regex_new ("\\r\\n" CREG11 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
> +    g_assert (regex);
> +    g_ptr_array_add (array, regex);
> +
>      /* #4 */
>      if (solicited)
>          regex = g_regex_new (CREG4 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
> @@ -867,7 +876,7 @@ mm_3gpp_parse_cgdcont_test_response (const gchar *response,
>          return NULL;
>      }
>  
> -    r = g_regex_new ("\\+CGDCONT:\\s*\\((\\d+)-?(\\d+)?\\),\\(?\"(\\S+)\"",
> +    r = g_regex_new ("\\+CGDCONT:\\s*\\(\\s*(\\d+)\\s*-?\\s*(\\d+)?\\s*\\)\\s*,\\s*\\(?\"(\\S+)\"",
>                       G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
>                       0, &inner_error);
>      g_assert (r != NULL);
> @@ -955,7 +964,7 @@ mm_3gpp_parse_cgdcont_read_response (const gchar *reply,
>          return NULL;
>  
>      list = NULL;
> -    r = g_regex_new ("\\+CGDCONT:\\s*(\\d+)\\s*,([^,\\)]*),([^,\\)]*),([^,\\)]*)",
> +    r = g_regex_new ("\\+CGDCONT:\\s*(\\d+)\\s*,([^, \\)]*)\\s*,([^, \\)]*)\\s*,([^, \\)]*)",
>                       G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
>                       0, &inner_error);
>      if (r) {
> _______________________________________________
> ModemManager-devel mailing list
> ModemManager-devel at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/modemmanager-devel
> 


-- 
Aleksander
https://aleksander.es


More information about the ModemManager-devel mailing list