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/option | |
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/option')
-rw-r--r-- | src/plugins/option/mm-broadband-bearer-hso.c | 818 | ||||
-rw-r--r-- | src/plugins/option/mm-broadband-bearer-hso.h | 61 | ||||
-rw-r--r-- | src/plugins/option/mm-broadband-modem-hso.c | 788 | ||||
-rw-r--r-- | src/plugins/option/mm-broadband-modem-hso.h | 51 | ||||
-rw-r--r-- | src/plugins/option/mm-broadband-modem-option.c | 1228 | ||||
-rw-r--r-- | src/plugins/option/mm-broadband-modem-option.h | 51 | ||||
-rw-r--r-- | src/plugins/option/mm-plugin-hso.c | 202 | ||||
-rw-r--r-- | src/plugins/option/mm-plugin-hso.h | 42 | ||||
-rw-r--r-- | src/plugins/option/mm-plugin-option.c | 121 | ||||
-rw-r--r-- | src/plugins/option/mm-plugin-option.h | 42 | ||||
-rw-r--r-- | src/plugins/option/mm-shared-option.c | 77 | ||||
-rw-r--r-- | src/plugins/option/mm-shared-option.h | 49 | ||||
-rw-r--r-- | src/plugins/option/mm-shared.c | 20 | ||||
-rw-r--r-- | src/plugins/option/mm-sim-option.c | 84 | ||||
-rw-r--r-- | src/plugins/option/mm-sim-option.h | 51 |
15 files changed, 3685 insertions, 0 deletions
diff --git a/src/plugins/option/mm-broadband-bearer-hso.c b/src/plugins/option/mm-broadband-bearer-hso.c new file mode 100644 index 00000000..9199e7b3 --- /dev/null +++ b/src/plugins/option/mm-broadband-bearer-hso.c @@ -0,0 +1,818 @@ +/* -*- 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) 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 <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-hso.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-daemon-enums-types.h" + +G_DEFINE_TYPE (MMBroadbandBearerHso, mm_broadband_bearer_hso, MM_TYPE_BROADBAND_BEARER); + +struct _MMBroadbandBearerHsoPrivate { + guint auth_idx; + + GTask *connect_pending; + guint connect_pending_id; + gulong connect_port_closed_id; +}; + +/*****************************************************************************/ +/* 3GPP IP config retrieval (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + guint cid; +} GetIpConfig3gppContext; + +static void +get_ip_config_context_free (GetIpConfig3gppContext *ctx) +{ + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_slice_free (GetIpConfig3gppContext, ctx); +} + +static gboolean +get_ip_config_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + MMBearerIpConfig **ipv4_config, + MMBearerIpConfig **ipv6_config, + GError **error) +{ + MMBearerIpConfig *ip_config; + + ip_config = g_task_propagate_pointer (G_TASK (res), error); + if (!ip_config) + return FALSE; + + /* No IPv6 for now */ + *ipv4_config = ip_config; /* Transfer ownership */ + *ipv6_config = NULL; + return TRUE; +} + +#define OWANDATA_TAG "_OWANDATA: " + +static void +ip_config_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GetIpConfig3gppContext *ctx; + MMBearerIpConfig *ip_config = NULL; + const gchar *response; + GError *error = NULL; + gchar **items; + gchar *dns[3] = { 0 }; + guint i; + guint dns_i; + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* TODO: use a regex to parse this */ + + /* Check result */ + if (!g_str_has_prefix (response, OWANDATA_TAG)) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get IP config: invalid response '%s'", + response); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + response = mm_strip_tag (response, OWANDATA_TAG); + items = g_strsplit (response, ", ", 0); + + for (i = 0, dns_i = 0; items[i]; i++) { + if (i == 0) { /* CID */ + guint num; + + if (!mm_get_uint_from_str (items[i], &num) || + num != ctx->cid) { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unknown CID in OWANDATA response (" + "got %d, expected %d)", (guint) num, ctx->cid); + break; + } + } else if (i == 1) { /* IP address */ + guint32 tmp; + + if (!inet_pton (AF_INET, items[i], &tmp)) + break; + + ip_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ip_config, MM_BEARER_IP_METHOD_STATIC); + mm_bearer_ip_config_set_address (ip_config, items[i]); + mm_bearer_ip_config_set_prefix (ip_config, 32); + } else if (i == 3 || i == 4) { /* DNS entries */ + guint32 tmp; + + if (!inet_pton (AF_INET, items[i], &tmp)) { + g_clear_object (&ip_config); + break; + } + + dns[dns_i++] = items[i]; + } + } + + if (!ip_config) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get IP config: couldn't parse response '%s'", + response); + } else { + /* If we got DNS entries, set them in the IP config */ + if (dns[0]) + mm_bearer_ip_config_set_dns (ip_config, (const gchar **)dns); + + g_task_return_pointer (task, ip_config, g_object_unref); + } + + g_object_unref (task); + g_strfreev (items); +} + +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; + gchar *command; + + ctx = g_slice_new0 (GetIpConfig3gppContext); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)get_ip_config_context_free); + + command = g_strdup_printf ("AT_OWANDATA=%d", cid); + mm_base_modem_at_command_full ( + MM_BASE_MODEM (modem), + primary, + command, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)ip_config_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; + guint cid; + MMPort *data; + guint auth_idx; + GError *saved_error; +} Dial3gppContext; + +static void +dial_3gpp_context_free (Dial3gppContext *ctx) +{ + 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 guint +dial_3gpp_get_connecting_cid (GTask *task) +{ + Dial3gppContext *ctx; + + ctx = g_task_get_task_data (task); + return ctx->cid; +} + +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 (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + Dial3gppContext *ctx; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_full_finish (modem, 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) +{ + Dial3gppContext *ctx; + gchar *command; + + ctx = g_task_get_task_data (task); + + /* Need to reset the connection attempt */ + command = g_strdup_printf ("AT_OWANCALL=%d,0,1", ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)connect_reset_ready, + task); + g_free (command); +} + +static void +process_pending_connect_attempt (MMBroadbandBearerHso *self, + MMBearerConnectionStatus status) +{ + GTask *task; + Dial3gppContext *ctx; + + /* Recover task and remove both cancellation and timeout (if any)*/ + g_assert (self->priv->connect_pending); + task = self->priv->connect_pending; + self->priv->connect_pending = NULL; + + ctx = g_task_get_task_data (task); + + if (self->priv->connect_pending_id) { + g_source_remove (self->priv->connect_pending_id); + self->priv->connect_pending_id = 0; + } + + if (self->priv->connect_port_closed_id) { + g_signal_handler_disconnect (ctx->primary, self->priv->connect_port_closed_id); + self->priv->connect_port_closed_id = 0; + } + + /* Reporting connected */ + 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; + } + + /* Received CONNECTION_FAILED or DISCONNECTED during a connection attempt, + * so return a failed error. Note that if the cancellable has been cancelled + * already, a cancelled error would be returned instead. */ + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Call setup failed"); + g_object_unref (task); +} + +static gboolean +connect_timed_out_cb (MMBroadbandBearerHso *self) +{ + GTask *task; + Dial3gppContext *ctx; + + /* Cleanup timeout ID */ + self->priv->connect_pending_id = 0; + + /* Recover task and own it */ + task = self->priv->connect_pending; + self->priv->connect_pending = NULL; + g_assert (task); + + ctx = g_task_get_task_data (task); + + /* Remove closed port watch, if found */ + if (self->priv->connect_port_closed_id) { + g_signal_handler_disconnect (ctx->primary, self->priv->connect_port_closed_id); + self->priv->connect_port_closed_id = 0; + } + + /* Setup error to return after the reset */ + 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"); + + /* It's probably pointless to try to reset this here, but anyway... */ + connect_reset (task); + + return G_SOURCE_REMOVE; +} + +static void +forced_close_cb (MMBroadbandBearerHso *self) +{ + /* Just treat the forced close event as any other unsolicited message */ + mm_base_bearer_report_connection_status (MM_BASE_BEARER (self), + MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED); +} + +static void +activate_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerHso *self) +{ + GTask *task; + Dial3gppContext *ctx; + GError *error = NULL; + + task = g_steal_pointer (&self->priv->connect_pending); + + /* Try to recover the connection task. If none found, it means the + * task was already completed and we have nothing else to do. + * But note that we won't take owneship of the task yet! */ + 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 + * about 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 */ + + /* Errors on the dial command are fatal */ + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + goto out; + } + + /* Track the task again */ + self->priv->connect_pending = task; + + /* We will now setup a timeout and keep the context in the bearer's private. + * Reports of modem being connected will arrive via unsolicited messages. + * This timeout should be long enough. Actually... ideally should never get + * reached. */ + self->priv->connect_pending_id = g_timeout_add_seconds (MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + (GSourceFunc)connect_timed_out_cb, + self); + + /* If we get the port closed, we treat as a connect error */ + ctx = g_task_get_task_data (task); + self->priv->connect_port_closed_id = g_signal_connect_swapped (ctx->primary, + "forced-close", + G_CALLBACK (forced_close_cb), + self); + + out: + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); +} + +static void authenticate (GTask *task); + +static void +authenticate_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerHso *self; + Dial3gppContext *ctx; + gchar *command; + + /* If cancelled, complete */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_full_finish (modem, res, NULL)) { + /* Try the next auth command */ + ctx->auth_idx++; + authenticate (task); + return; + } + + /* Store which auth command worked, for next attempts */ + self->priv->auth_idx = ctx->auth_idx; + + /* The unsolicited response to AT_OWANCALL 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. Note + * also that we do NOT pass the ctx to the GAsyncReadyCallback, as it + * may not be valid any more when the callback is called (it may be + * already completed in the unsolicited handling) */ + g_assert (self->priv->connect_pending == NULL); + self->priv->connect_pending = task; + + /* Success, activate the PDP context and start the data session */ + command = g_strdup_printf ("AT_OWANCALL=%d,1,1", ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback) activate_ready, + g_object_ref (self)); /* we pass the bearer object! */ + g_free (command); +} + +const gchar *auth_commands[] = { + "$QCPDPP", + /* Icera-based devices (GI0322/Quicksilver, iCON 505) don't implement + * $QCPDPP, but instead use _OPDPP with the same arguments. + */ + "_OPDPP", + NULL +}; + +static void +authenticate (GTask *task) +{ + MMBroadbandBearerHso *self; + Dial3gppContext *ctx; + gchar *command; + const gchar *user; + const gchar *password; + MMBearerAllowedAuth allowed_auth; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + if (!auth_commands[ctx->auth_idx]) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't run HSO authentication"); + g_object_unref (task); + return; + } + + 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))); + allowed_auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + + /* Both user and password are required; otherwise firmware returns an error */ + if (!user || !password || allowed_auth == MM_BEARER_ALLOWED_AUTH_NONE) { + mm_obj_dbg (self, "not using authentication"); + command = g_strdup_printf ("%s=%d,0", + auth_commands[ctx->auth_idx], + ctx->cid); + } else { + gchar *quoted_user; + gchar *quoted_password; + guint hso_auth; + + if (allowed_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN) { + mm_obj_dbg (self, "using default (CHAP) authentication method"); + hso_auth = 2; + } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_CHAP) { + mm_obj_dbg (self, "using CHAP authentication method"); + hso_auth = 2; + } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_PAP) { + mm_obj_dbg (self, "using PAP authentication method"); + hso_auth = 1; + } else { + gchar *str; + + str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot use any of the specified authentication methods (%s)", + str); + g_object_unref (task); + g_free (str); + return; + } + + quoted_user = mm_port_serial_at_quote_string (user); + quoted_password = mm_port_serial_at_quote_string (password); + command = g_strdup_printf ("%s=%d,%u,%s,%s", + auth_commands[ctx->auth_idx], + ctx->cid, + hso_auth, + quoted_password, + quoted_user); + g_free (quoted_user); + g_free (quoted_password); + } + + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)authenticate_ready, + task); + g_free (command); +} + +static void +dial_3gpp (MMBroadbandBearer *_self, + MMBaseModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandBearerHso *self = MM_BROADBAND_BEARER_HSO (_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); + + /* Always start with the index that worked last time + * (will be 0 the first time)*/ + ctx->auth_idx = self->priv->auth_idx; + + /* 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 disconnect */ + +typedef struct { + MMBaseModem *modem; + MMPortSerialAt *primary; +} DisconnectContext; + +static void +disconnect_context_free (DisconnectContext *ctx) +{ + g_object_unref (ctx->primary); + g_object_unref (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 +disconnect_owancall_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearerHso *self; + GError *error = NULL; + + self = g_task_get_source_object (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); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +disconnect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + gchar *command; + DisconnectContext *ctx; + GTask *task; + + g_assert (primary != NULL); + + ctx = g_new0 (DisconnectContext, 1); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->primary = g_object_ref (primary); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)disconnect_context_free); + + /* Use specific CID */ + command = g_strdup_printf ("AT_OWANCALL=%d,0,0", cid); + mm_base_modem_at_command_full (MM_BASE_MODEM (modem), + primary, + command, + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)disconnect_owancall_ready, + task); + g_free (command); +} + +/*****************************************************************************/ + +gint +mm_broadband_bearer_hso_get_connecting_profile_id (MMBroadbandBearerHso *self) +{ + return (self->priv->connect_pending ? + (gint)dial_3gpp_get_connecting_cid (self->priv->connect_pending) : + MM_3GPP_PROFILE_ID_UNKNOWN); +} + +/*****************************************************************************/ + +static void +report_connection_status (MMBaseBearer *_self, + MMBearerConnectionStatus status, + const GError *connection_error) +{ + MMBroadbandBearerHso *self = MM_BROADBAND_BEARER_HSO (_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; + } + + mm_obj_dbg (self, "received spontaneous _OWANCALL (%s)", + mm_bearer_connection_status_get_string (status)); + + if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) { + /* If no connection attempt on-going, make sure we mark ourselves as + * disconnected */ + MM_BASE_BEARER_CLASS (mm_broadband_bearer_hso_parent_class)->report_connection_status (_self, status,connection_error); + } +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_broadband_bearer_hso_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_hso_new (MMBroadbandModemHso *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_HSO, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + NULL); +} + +static void +mm_broadband_bearer_hso_init (MMBroadbandBearerHso *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_BEARER_HSO, + MMBroadbandBearerHsoPrivate); +} + +static void +mm_broadband_bearer_hso_class_init (MMBroadbandBearerHsoClass *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 (MMBroadbandBearerHsoPrivate)); + + 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/option/mm-broadband-bearer-hso.h b/src/plugins/option/mm-broadband-bearer-hso.h new file mode 100644 index 00000000..def46ac3 --- /dev/null +++ b/src/plugins/option/mm-broadband-bearer-hso.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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_BEARER_HSO_H +#define MM_BROADBAND_BEARER_HSO_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-hso.h" + +#define MM_TYPE_BROADBAND_BEARER_HSO (mm_broadband_bearer_hso_get_type ()) +#define MM_BROADBAND_BEARER_HSO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_HSO, MMBroadbandBearerHso)) +#define MM_BROADBAND_BEARER_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_HSO, MMBroadbandBearerHsoClass)) +#define MM_IS_BROADBAND_BEARER_HSO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_HSO)) +#define MM_IS_BROADBAND_BEARER_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_HSO)) +#define MM_BROADBAND_BEARER_HSO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_HSO, MMBroadbandBearerHsoClass)) + +typedef struct _MMBroadbandBearerHso MMBroadbandBearerHso; +typedef struct _MMBroadbandBearerHsoClass MMBroadbandBearerHsoClass; +typedef struct _MMBroadbandBearerHsoPrivate MMBroadbandBearerHsoPrivate; + +struct _MMBroadbandBearerHso { + MMBroadbandBearer parent; + MMBroadbandBearerHsoPrivate *priv; +}; + +struct _MMBroadbandBearerHsoClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_hso_get_type (void); + +/* Default 3GPP bearer creation implementation */ +void mm_broadband_bearer_hso_new (MMBroadbandModemHso *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_hso_new_finish (GAsyncResult *res, + GError **error); + +gint mm_broadband_bearer_hso_get_connecting_profile_id (MMBroadbandBearerHso *self); + +#endif /* MM_BROADBAND_BEARER_HSO_H */ diff --git a/src/plugins/option/mm-broadband-modem-hso.c b/src/plugins/option/mm-broadband-modem-hso.c new file mode 100644 index 00000000..a2cc1770 --- /dev/null +++ b/src/plugins/option/mm-broadband-modem-hso.c @@ -0,0 +1,788 @@ +/* -*- 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 hso) 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-modem-helpers.h" +#include "mm-log-object.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-location.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-hso.h" +#include "mm-broadband-bearer-hso.h" +#include "mm-bearer-list.h" +#include "mm-shared-option.h" + +static void shared_option_init (MMSharedOption *iface); +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); + +static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemHso, mm_broadband_modem_hso, MM_TYPE_BROADBAND_MODEM_OPTION, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_OPTION, shared_option_init) + 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)); + +struct _MMBroadbandModemHsoPrivate { + /* Regex for connected notifications */ + GRegex *_owancall_regex; + + MMModemLocationSource enabled_sources; +}; + +/*****************************************************************************/ +/* 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_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_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 +broadband_bearer_hso_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_hso_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) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + if (mm_bearer_properties_get_ip_type (properties) & + (MM_BEARER_IP_FAMILY_IPV6 | MM_BEARER_IP_FAMILY_IPV4V6)) { + mm_obj_dbg (self, "creating generic bearer (IPv6 requested)..."); + mm_broadband_bearer_new (MM_BROADBAND_MODEM (self), + properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_new_ready, + task); + return; + } + + mm_obj_dbg (self, "creating HSO bearer..."); + mm_broadband_bearer_hso_new (MM_BROADBAND_MODEM_HSO (self), + properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_hso_new_ready, + task); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) */ + +static MMUnlockRetries * +load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_unlock_retries_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + int pin1, puk1; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + response = mm_strip_tag (response, "_OERCN:"); + if (sscanf (response, " %d, %d", &pin1, &puk1) == 2) { + MMUnlockRetries *retries; + retries = mm_unlock_retries_new (); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1); + g_task_return_pointer (task, retries, g_object_unref); + } else { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid unlock retries response: '%s'", + response); + } + g_object_unref (task); +} + +static void +load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "_OERCN?", + 3, + FALSE, + (GAsyncReadyCallback)load_unlock_retries_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +typedef struct { + guint cid; + MMBearerConnectionStatus status; +} BearerListReportStatusForeachContext; + +static void +bearer_list_report_status_foreach (MMBaseBearer *bearer, + BearerListReportStatusForeachContext *ctx) +{ + gint profile_id; + gint connecting_profile_id; + + if (!MM_IS_BROADBAND_BEARER_HSO (bearer)) + return; + + /* The profile ID in the base bearer is set only once the modem is connected */ + profile_id = mm_base_bearer_get_profile_id (bearer); + + /* The profile ID in the hso bearer is available during the connecting phase */ + connecting_profile_id = mm_broadband_bearer_hso_get_connecting_profile_id (MM_BROADBAND_BEARER_HSO (bearer)); + + if ((profile_id != (gint)ctx->cid) && (connecting_profile_id != (gint)ctx->cid)) + return; + + mm_base_bearer_report_connection_status (MM_BASE_BEARER (bearer), ctx->status); +} + +static void +hso_connection_status_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemHso *self) +{ + g_autoptr(MMBearerList) list = NULL; + BearerListReportStatusForeachContext ctx; + guint cid; + guint status; + + /* Ensure we got proper parsed values */ + if (!mm_get_uint_from_match_info (match_info, 1, &cid) || + !mm_get_uint_from_match_info (match_info, 2, &status)) + return; + + /* Setup context */ + ctx.cid = cid; + ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN; + + switch (status) { + case 1: + ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED; + break; + case 3: + ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED; + break; + case 0: + ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED; + break; + default: + break; + } + + /* 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); + + /* Will report status only in the bearer with the specific CID */ + if (list) + mm_bearer_list_foreach (list, (MMBearerListForeachFunc)bearer_list_report_status_foreach, &ctx); +} + +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 */ + mm_port_serial_at_add_unsolicited_msg_handler ( + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex, + (MMPortSerialAtUnsolicitedMsgFn)hso_connection_status_changed, + self, + NULL); + + 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 */ + mm_port_serial_at_add_unsolicited_msg_handler ( + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex, + NULL, NULL, NULL); + + /* 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)); +} + +/*****************************************************************************/ +/* 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; + } + + /* Now our own check. + * + * We could issue AT_OIFACE? to list the interfaces currently enabled in the + * module, to see if there is a 'GPS' interface enabled. But we'll just go + * and see if there is already a 'GPS control' AT port and a raw serial 'GPS' + * port grabbed. + * + * NOTE: A deeper implementation could handle the situation where the GPS + * interface is found disabled in AT_OIFACE?. In this case, we could issue + * AT_OIFACE="GPS",1 to enable it (and AT_OIFACE="GPS",0 to disable it), but + * enabling/disabling GPS involves a complete reboot of the modem, which is + * probably not the desired thing here. + */ + if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)) && + mm_base_modem_peek_port_gps_control (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; + + mm_base_modem_at_command_full_finish (self, res, &error); + + 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)) { + /* 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) +{ + MMBroadbandModemHso *hso = MM_BROADBAND_MODEM_HSO (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)) { + hso->priv->enabled_sources &= ~source; + + if (!(hso->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) { + /* We enable continuous GPS fixes with AT_OGPS=0 */ + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)), + "_OGPS=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 */ + +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; + + 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)) { + MMPortSerialGps *gps_port; + + 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 + 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) +{ + MMBroadbandModemHso *self = MM_BROADBAND_MODEM_HSO (_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 */ + + ctx = g_task_get_task_data (task); + + /* NMEA, RAW and UNMANAGED are all enabled in the same way */ + 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. + * NOTE: interface already takes care of making sure that raw/nmea and + * unmanaged are not enabled at the same time */ + 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) { + /* We enable continuous GPS fixes with AT_OGPS=2 */ + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)), + "_OGPS=2", + 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 +trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +static void +setup_ports (MMBroadbandModem *self) +{ + MMPortSerialAt *gps_control_port; + MMPortSerialGps *gps_data_port; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_hso_parent_class)->setup_ports (self); + + /* _OWANCALL unsolicited messages are only expected in the primary port. */ + mm_port_serial_at_add_unsolicited_msg_handler ( + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex, + NULL, NULL, NULL); + + g_object_set (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + MM_PORT_SERIAL_SEND_DELAY, (guint64) 0, + /* built-in echo removal conflicts with unsolicited _OWANCALL + * messages, which are not <CR><LF> prefixed. */ + MM_PORT_SERIAL_AT_REMOVE_ECHO, FALSE, + NULL); + + gps_control_port = mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)); + gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_control_port && gps_data_port) { + /* It may happen that the modem was started with GPS already enabled, or + * maybe ModemManager got rebooted and it was left enabled before. We'll make + * sure that it is disabled when we initialize the modem */ + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + gps_control_port, + "_OGPS=0", + 3, FALSE, FALSE, NULL, NULL, NULL); + + /* Add handler for the NMEA traces */ + mm_port_serial_gps_add_trace_handler (gps_data_port, + (MMPortSerialGpsTraceFn)trace_received, + self, + NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemHso * +mm_broadband_modem_hso_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_HSO, + 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, + /* Generic bearer (AT) and HSO bearer (NET) supported */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + NULL); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemHso *self = MM_BROADBAND_MODEM_HSO (object); + + g_regex_unref (self->priv->_owancall_regex); + + G_OBJECT_CLASS (mm_broadband_modem_hso_parent_class)->finalize (object); +} + +static void +mm_broadband_modem_hso_init (MMBroadbandModemHso *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_HSO, + MMBroadbandModemHsoPrivate); + + self->priv->_owancall_regex = g_regex_new ("_OWANCALL: (\\d),\\s*(\\d)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE; +} + +static void +shared_option_init (MMSharedOption *iface) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface->create_sim = mm_shared_option_create_sim; + iface->create_sim_finish = mm_shared_option_create_sim_finish; + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; + + /* HSO modems don't need the extra 10s wait after powering up */ + iface->modem_after_power_up = NULL; + iface->modem_after_power_up_finish = NULL; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + 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_hso_class_init (MMBroadbandModemHsoClass *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 (MMBroadbandModemHsoPrivate)); + + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/option/mm-broadband-modem-hso.h b/src/plugins/option/mm-broadband-modem-hso.h new file mode 100644 index 00000000..b918719c --- /dev/null +++ b/src/plugins/option/mm-broadband-modem-hso.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 hso) 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_HSO_H +#define MM_BROADBAND_MODEM_HSO_H + +#include "mm-broadband-modem-option.h" + +#define MM_TYPE_BROADBAND_MODEM_HSO (mm_broadband_modem_hso_get_type ()) +#define MM_BROADBAND_MODEM_HSO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_HSO, MMBroadbandModemHso)) +#define MM_BROADBAND_MODEM_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_HSO, MMBroadbandModemHsoClass)) +#define MM_IS_BROADBAND_MODEM_HSO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_HSO)) +#define MM_IS_BROADBAND_MODEM_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_HSO)) +#define MM_BROADBAND_MODEM_HSO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_HSO, MMBroadbandModemHsoClass)) + +typedef struct _MMBroadbandModemHso MMBroadbandModemHso; +typedef struct _MMBroadbandModemHsoClass MMBroadbandModemHsoClass; +typedef struct _MMBroadbandModemHsoPrivate MMBroadbandModemHsoPrivate; + +struct _MMBroadbandModemHso { + MMBroadbandModemOption parent; + MMBroadbandModemHsoPrivate *priv; +}; + +struct _MMBroadbandModemHsoClass{ + MMBroadbandModemOptionClass parent; +}; + +GType mm_broadband_modem_hso_get_type (void); + +MMBroadbandModemHso *mm_broadband_modem_hso_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_HSO_H */ diff --git a/src/plugins/option/mm-broadband-modem-option.c b/src/plugins/option/mm-broadband-modem-option.c new file mode 100644 index 00000000..dcecd5b0 --- /dev/null +++ b/src/plugins/option/mm-broadband-modem-option.c @@ -0,0 +1,1228 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-modem-helpers.h" +#include "mm-log-object.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-option.h" +#include "mm-shared-option.h" + +static void shared_option_init (MMSharedOption *iface); +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemOption, mm_broadband_modem_option, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_OPTION, shared_option_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)) + +struct _MMBroadbandModemOptionPrivate { + /* Regex for access-technology related notifications */ + GRegex *_ossysi_regex; + GRegex *_octi_regex; + GRegex *_ouwcti_regex; + + /* Regex for signal quality related notifications */ + GRegex *_osigq_regex; + + /* Regex for other notifications to ignore */ + GRegex *ignore_regex; + + guint after_power_up_wait_id; +}; + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMModemModeCombination mode; + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G, 2G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + /* 2G and 3G, 3G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + g_task_new (self, NULL, 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) +{ + const gchar *response; + const gchar *str; + gint a, b; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + str = mm_strip_tag (response, "_OPSYS:"); + + if (!sscanf (str, "%d,%d", &a, &b)) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse OPSYS response: '%s'", + response); + return FALSE; + } + + switch (a) { + case 0: + *allowed = MM_MODEM_MODE_2G; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + case 1: + *allowed = MM_MODEM_MODE_3G; + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + case 2: + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_2G; + return TRUE; + case 3: + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_3G; + return TRUE; + case 5: /* any */ + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + *preferred = MM_MODEM_MODE_NONE; + return TRUE; + default: + break; + } + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse unexpected OPSYS response: '%s'", + response); + return FALSE; +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "_OPSYS?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +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) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (self, res, &error); + if (error) + /* Let the error be critical. */ + g_task_return_error (task, error); + else + 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) +{ + GTask *task; + gchar *command; + gint option_mode = -1; + + task = g_task_new (self, NULL, callback, user_data); + + if (allowed == MM_MODEM_MODE_2G) + option_mode = 0; + else if (allowed == MM_MODEM_MODE_3G) + option_mode = 1; + else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { + if (preferred == MM_MODEM_MODE_2G) + option_mode = 2; + else if (preferred == MM_MODEM_MODE_3G) + option_mode = 3; + else /* none preferred, so AUTO */ + option_mode = 5; + } else if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE) + option_mode = 5; + + if (option_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 ("AT_OPSYS=%d,2", option_mode); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)allowed_mode_update_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +typedef enum { + ACCESS_TECHNOLOGIES_STEP_FIRST, + ACCESS_TECHNOLOGIES_STEP_OSSYS, + ACCESS_TECHNOLOGIES_STEP_OCTI, + ACCESS_TECHNOLOGIES_STEP_OWCTI, + ACCESS_TECHNOLOGIES_STEP_LAST +} AccessTechnologiesStep; + +typedef struct { + MMModemAccessTechnology access_technology; + gboolean check_2g; + gboolean check_3g; + AccessTechnologiesStep step; +} AccessTechnologiesContext; + +static void load_access_technologies_step (GTask *task); + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + 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 FALSE; + } + + /* We are reporting ALL 3GPP access technologies here */ + *access_technologies = (MMModemAccessTechnology) value; + *mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK; + return TRUE; +} + +static gboolean +ossys_to_mm (gchar ossys, + MMModemAccessTechnology *access_technology) +{ + if (ossys == '0') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + return TRUE; + } + + if (ossys == '2') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + return TRUE; + } + + if (ossys == '3') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + return TRUE; + } + + return FALSE; +} + +static gboolean +parse_ossys_response (const gchar *response, + MMModemAccessTechnology *access_technology) +{ + MMModemAccessTechnology current = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + const gchar *p; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + p = mm_strip_tag (response, "_OSSYS:"); + r = g_regex_new ("(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL); + g_assert (r != NULL); + + g_regex_match (r, p, 0, &match_info); + if (g_match_info_matches (match_info)) { + g_autofree gchar *str = NULL; + + str = g_match_info_fetch (match_info, 2); + if (str && ossys_to_mm (str[0], ¤t)) { + *access_technology = current; + return TRUE; + } + } + return FALSE; +} + +static void +ossys_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + AccessTechnologiesContext *ctx; + const gchar *response; + + ctx = g_task_get_task_data (task); + + /* If for some reason the OSSYS request failed, still try to check + * explicit 2G/3G mode with OCTI and OWCTI; maybe we'll get something. + */ + response = mm_base_modem_at_command_finish (self, res, NULL); + /* Response is _OSSYS: <n>,<act> so we must skip the <n> */ + if (response && + parse_ossys_response (response, &ctx->access_technology)) { + /* If the OSSYS response indicated a generic access tech type + * then only check for more specific access tech of that type. + */ + if (ctx->access_technology == MM_MODEM_ACCESS_TECHNOLOGY_GPRS) + ctx->check_3g = FALSE; + else if (ctx->access_technology == MM_MODEM_ACCESS_TECHNOLOGY_UMTS) + ctx->check_2g = FALSE; + } + + /* Go on to next step */ + ctx->step++; + load_access_technologies_step (task); +} + +static gboolean +octi_to_mm (gchar octi, + MMModemAccessTechnology *access_technology) +{ + if (octi == '1') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GSM; + return TRUE; + } + + if (octi == '2') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + return TRUE; + } + + if (octi == '3') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + return TRUE; + } + + return FALSE; +} + +static gboolean +parse_octi_response (const gchar *response, + MMModemAccessTechnology *access_technology) +{ + MMModemAccessTechnology current = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + const gchar *p; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + p = mm_strip_tag (response, "_OCTI:"); + r = g_regex_new ("(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL); + g_assert (r != NULL); + + g_regex_match (r, p, 0, &match_info); + if (g_match_info_matches (match_info)) { + g_autofree gchar *str = NULL; + + str = g_match_info_fetch (match_info, 2); + if (str && octi_to_mm (str[0], ¤t)) { + *access_technology = current; + return TRUE; + } + } + return FALSE; +} + +static void +octi_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + AccessTechnologiesContext *ctx; + MMModemAccessTechnology octi = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + const gchar *response; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (self, res, NULL); + if (response && + parse_octi_response (response, &octi)) { + /* If current tech is 2G or unknown then use the more specific + * OCTI response. + */ + if (ctx->access_technology < MM_MODEM_ACCESS_TECHNOLOGY_UMTS) + ctx->access_technology = octi; + } + + /* Go on to next step */ + ctx->step++; + load_access_technologies_step (task); +} + +static gboolean +owcti_to_mm (gchar owcti, MMModemAccessTechnology *access_technology) +{ + if (owcti == '1') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + return TRUE; + } + + if (owcti == '2') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; + return TRUE; + } + + if (owcti == '3') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSUPA; + return TRUE; + } + + if (owcti == '4') { + *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSPA; + return TRUE; + } + + return FALSE; +} + +static gboolean +parse_owcti_response (const gchar *response, + MMModemAccessTechnology *access_technology) +{ + response = mm_strip_tag (response, "_OWCTI:"); + return owcti_to_mm (*response, access_technology); +} + +static void +owcti_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + AccessTechnologiesContext *ctx; + MMModemAccessTechnology owcti = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + const gchar *response; + + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (self, res, NULL); + if (response && + parse_owcti_response (response, &owcti)) { + ctx->access_technology = owcti; + } + + /* Go on to next step */ + ctx->step++; + load_access_technologies_step (task); +} + +static void +load_access_technologies_step (GTask *task) +{ + MMBroadbandModemOption *self; + AccessTechnologiesContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case ACCESS_TECHNOLOGIES_STEP_FIRST: + ctx->step++; + /* fall through */ + + case ACCESS_TECHNOLOGIES_STEP_OSSYS: + mm_base_modem_at_command (MM_BASE_MODEM (self), + "_OSSYS?", + 3, + FALSE, + (GAsyncReadyCallback)ossys_query_ready, + task); + break; + + case ACCESS_TECHNOLOGIES_STEP_OCTI: + if (ctx->check_2g) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "_OCTI?", + 3, + FALSE, + (GAsyncReadyCallback)octi_query_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ACCESS_TECHNOLOGIES_STEP_OWCTI: + if (ctx->check_3g) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "_OWCTI?", + 3, + FALSE, + (GAsyncReadyCallback)owcti_query_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ACCESS_TECHNOLOGIES_STEP_LAST: + /* All done, set result and complete */ + g_task_return_int (task, ctx->access_technology); + g_object_unref (task); + break; + + default: + g_assert_not_reached (); + } +} + +static void +run_access_technology_loading_sequence (MMIfaceModem *self, + AccessTechnologiesStep first, + gboolean check_2g, + gboolean check_3g, + GAsyncReadyCallback callback, + gpointer user_data) +{ + AccessTechnologiesContext *ctx; + GTask *task; + + ctx = g_new (AccessTechnologiesContext, 1); + ctx->step = first; + ctx->check_2g = check_2g; + ctx->check_3g = check_3g; + ctx->access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + load_access_technologies_step (task); +} + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + run_access_technology_loading_sequence (self, + ACCESS_TECHNOLOGIES_STEP_FIRST, + TRUE, /* check 2g */ + TRUE, /* check 3g */ + callback, + user_data); +} + +/*****************************************************************************/ +/* After power up (Modem interface) */ + +static gboolean +modem_after_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +after_power_up_wait_cb (GTask *task) +{ + MMBroadbandModemOption *self; + + self = g_task_get_source_object (task); + self->priv->after_power_up_wait_id = 0; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + + return G_SOURCE_REMOVE; +} + +static void +modem_after_power_up (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemOption *self = MM_BROADBAND_MODEM_OPTION (_self); + + /* Some Option devices return OK on +CFUN=1 right away but need some time + * to finish initialization. + */ + g_warn_if_fail (self->priv->after_power_up_wait_id == 0); + self->priv->after_power_up_wait_id = + g_timeout_add_seconds (10, + (GSourceFunc)after_power_up_wait_cb, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* IMSI loading (3GPP interface) */ + +static gchar * +modem_3gpp_load_imei_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + gchar *imei; + gchar *comma; + + imei = g_strdup (mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error)); + if (!imei) + return NULL; + + /* IMEI reported by Option modems contain the IMEI plus something else: + * + * (ttyHS4): --> 'AT+CGSN<CR>' + * (ttyHS4): <-- '<CR><LF>357516032005989,TR19A8P11R<CR><LF><CR><LF>OK<CR><LF>' + */ + comma = strchr (imei, ','); + if (comma) + *comma = '\0'; + + return imei; +} + +static void +modem_3gpp_load_imei (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CGSN", + 3, + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static void +option_ossys_tech_changed (MMPortSerialAt *port, + GMatchInfo *info, + MMBroadbandModemOption *self) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + gchar *str; + + str = g_match_info_fetch (info, 1); + if (str) { + ossys_to_mm (str[0], &act); + g_free (str); + } + + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + act, + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); + + /* _OSSYSI only indicates general 2G/3G mode, so queue up some explicit + * access technology requests. + */ + if (act == MM_MODEM_ACCESS_TECHNOLOGY_GPRS) + run_access_technology_loading_sequence (MM_IFACE_MODEM (self), + ACCESS_TECHNOLOGIES_STEP_OCTI, + TRUE, /* check 2g */ + FALSE, /* check 3g */ + NULL, + NULL); + else if (act == MM_MODEM_ACCESS_TECHNOLOGY_UMTS) + run_access_technology_loading_sequence (MM_IFACE_MODEM (self), + ACCESS_TECHNOLOGIES_STEP_OWCTI, + FALSE, /* check 2g */ + TRUE, /* check 3g */ + NULL, + NULL); +} + +static void +option_2g_tech_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemOption *self) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + gchar *str; + + str = g_match_info_fetch (match_info, 1); + if (str && octi_to_mm (str[0], &act)) + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + act, + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); + g_free (str); +} + +static void +option_3g_tech_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemOption *self) +{ + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + gchar *str; + + str = g_match_info_fetch (match_info, 1); + if (str && owcti_to_mm (str[0], &act)) + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + act, + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); + g_free (str); +} + +static void +option_signal_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemOption *self) +{ + gchar *str; + guint quality = 0; + + str = g_match_info_fetch (match_info, 1); + if (str) { + quality = atoi (str); + g_free (str); + } + + if (quality == 99) { + /* 99 means unknown */ + quality = 0; + } else { + /* Normalize the quality */ + quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31; + } + + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); +} + +static void +set_unsolicited_events_handlers (MMBroadbandModemOption *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->_ossysi_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)option_ossys_tech_changed : NULL, + enable ? self : NULL, + NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->_octi_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)option_2g_tech_changed : NULL, + enable ? self : NULL, + NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->_ouwcti_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)option_3g_tech_changed : NULL, + enable ? self : NULL, + NULL); + + /* Signal quality related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->_osigq_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)option_signal_changed : NULL, + enable ? self : NULL, + NULL); + + /* Other unsolicited events to always ignore */ + if (!enable) + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->ignore_regex, + NULL, 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_OPTION (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_OPTION (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[] = { + { "_OSSYS=1", 3, FALSE, NULL }, + { "_OCTI=1", 3, FALSE, NULL }, + { "_OUWCTI=1", 3, FALSE, NULL }, + { "_OSQI=1", 3, 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); + } + + /* 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 const MMBaseModemAtCommand unsolicited_disable_sequence[] = { + { "_OSSYS=0", 3, FALSE, NULL }, + { "_OCTI=0", 3, FALSE, NULL }, + { "_OUWCTI=0", 3, FALSE, NULL }, + { "_OSQI=0", 3, FALSE, NULL }, + { NULL } +}; + +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 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)); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +static void +setup_ports (MMBroadbandModem *self) +{ + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_option_parent_class)->setup_ports (self); + + /* Now reset the unsolicited messages we'll handle when enabled */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_OPTION (self), FALSE); +} + +/*****************************************************************************/ + +static gboolean +is_nozomi (const gchar **drivers) +{ + if (drivers) { + guint i; + + for (i = 0; drivers[i]; i++) { + if (g_str_equal (drivers[i], "nozomi")) + return TRUE; + } + } + + return FALSE; +} + +MMBroadbandModemOption * +mm_broadband_modem_option_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + MMModem3gppFacility ignored; + + /* Ignore PH-SIM facility in 'nozomi' managed modems */ + ignored = is_nozomi (drivers) ? MM_MODEM_3GPP_FACILITY_PH_SIM : MM_MODEM_3GPP_FACILITY_NONE; + + return g_object_new (MM_TYPE_BROADBAND_MODEM_OPTION, + 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, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + MM_IFACE_MODEM_3GPP_IGNORED_FACILITY_LOCKS, ignored, + NULL); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemOption *self = MM_BROADBAND_MODEM_OPTION (object); + + g_regex_unref (self->priv->_ossysi_regex); + g_regex_unref (self->priv->_octi_regex); + g_regex_unref (self->priv->_ouwcti_regex); + g_regex_unref (self->priv->_osigq_regex); + g_regex_unref (self->priv->ignore_regex); + + G_OBJECT_CLASS (mm_broadband_modem_option_parent_class)->finalize (object); +} + +static void +mm_broadband_modem_option_init (MMBroadbandModemOption *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_OPTION, + MMBroadbandModemOptionPrivate); + self->priv->after_power_up_wait_id = 0; + + /* Prepare regular expressions to setup */ + self->priv->_ossysi_regex = g_regex_new ("\\r\\n_OSSYSI:\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->_octi_regex = g_regex_new ("\\r\\n_OCTI:\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->_ouwcti_regex = g_regex_new ("\\r\\n_OUWCTI:\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->_osigq_regex = g_regex_new ("\\r\\n_OSIGQ:\\s*(\\d+),(\\d)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->ignore_regex = g_regex_new ("\\r\\n\\+PACSP0\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +static void +shared_option_init (MMSharedOption *iface) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->create_sim = mm_shared_option_create_sim; + iface->create_sim_finish = mm_shared_option_create_sim_finish; + iface->modem_after_power_up = modem_after_power_up; + iface->modem_after_power_up_finish = modem_after_power_up_finish; + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_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; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->load_imei = modem_3gpp_load_imei; + iface->load_imei_finish = modem_3gpp_load_imei_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; + 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; +} + +static void +mm_broadband_modem_option_class_init (MMBroadbandModemOptionClass *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 (MMBroadbandModemOptionPrivate)); + + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/option/mm-broadband-modem-option.h b/src/plugins/option/mm-broadband-modem-option.h new file mode 100644 index 00000000..faf0595e --- /dev/null +++ b/src/plugins/option/mm-broadband-modem-option.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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_OPTION_H +#define MM_BROADBAND_MODEM_OPTION_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_OPTION (mm_broadband_modem_option_get_type ()) +#define MM_BROADBAND_MODEM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_OPTION, MMBroadbandModemOption)) +#define MM_BROADBAND_MODEM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_OPTION, MMBroadbandModemOptionClass)) +#define MM_IS_BROADBAND_MODEM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_OPTION)) +#define MM_IS_BROADBAND_MODEM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_OPTION)) +#define MM_BROADBAND_MODEM_OPTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_OPTION, MMBroadbandModemOptionClass)) + +typedef struct _MMBroadbandModemOption MMBroadbandModemOption; +typedef struct _MMBroadbandModemOptionClass MMBroadbandModemOptionClass; +typedef struct _MMBroadbandModemOptionPrivate MMBroadbandModemOptionPrivate; + +struct _MMBroadbandModemOption { + MMBroadbandModem parent; + MMBroadbandModemOptionPrivate *priv; +}; + +struct _MMBroadbandModemOptionClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_option_get_type (void); + +MMBroadbandModemOption *mm_broadband_modem_option_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_OPTION_H */ diff --git a/src/plugins/option/mm-plugin-hso.c b/src/plugins/option/mm-plugin-hso.c new file mode 100644 index 00000000..9a28ca64 --- /dev/null +++ b/src/plugins/option/mm-plugin-hso.c @@ -0,0 +1,202 @@ +/* -*- 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 hso) 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-private-boxed-types.h" +#include "mm-plugin-hso.h" +#include "mm-broadband-modem-hso.h" +#include "mm-log-object.h" + +G_DEFINE_TYPE (MMPluginHso, mm_plugin_hso, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ +/* Custom init */ + +#define TAG_HSO_AT_CONTROL "hso-at-control" +#define TAG_HSO_AT_APP "hso-at-app" +#define TAG_HSO_AT_MODEM "hso-at-modem" +#define TAG_HSO_AT_GPS_CONTROL "hso-at-gps-control" +#define TAG_HSO_GPS "hso-gps" +#define TAG_HSO_DIAG "hso-diag" + +static gboolean +hso_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +hso_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMKernelDevice *kernel_port; + GTask *task; + const gchar *subsys, *sysfs_path; + + subsys = mm_port_probe_get_port_subsys (probe); + kernel_port = mm_port_probe_peek_port (probe); + sysfs_path = mm_kernel_device_get_sysfs_path (kernel_port); + + if (g_str_equal (subsys, "tty")) { + gchar *hsotype_path; + gchar *contents = NULL; + + hsotype_path = g_build_filename (sysfs_path, "hsotype", NULL); + if (g_file_get_contents (hsotype_path, &contents, NULL, NULL)) { + mm_obj_dbg (probe, "HSO port type %s: %s", hsotype_path, contents); + if (g_str_has_prefix (contents, "Control")) { + g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_CONTROL, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, TRUE); + } else if (g_str_has_prefix (contents, "Application")) { + g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_APP, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, TRUE); + } else if (g_str_has_prefix (contents, "Modem")) { + g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_MODEM, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, TRUE); + } else if (g_str_has_prefix (contents, "GPS Control")) { + g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_GPS_CONTROL, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, TRUE); + } else if (g_str_has_prefix (contents, "GPS")) { + /* Not an AT port, but the port to grab GPS traces */ + g_object_set_data (G_OBJECT (probe), TAG_HSO_GPS, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, FALSE); + mm_port_probe_set_result_qcdm (probe, FALSE); + } else if (g_str_has_prefix (contents, "Diag")) { + g_object_set_data (G_OBJECT (probe), TAG_HSO_DIAG, GUINT_TO_POINTER (TRUE)); + mm_port_probe_set_result_at (probe, FALSE); + + /* Don't automatically tag as QCDM, as the 'hso' driver reports + * a DIAG port for some Icera-based modems, which don't have + * QCDM ports since they aren't made by Qualcomm. + */ + } + g_free (contents); + } + g_free (hsotype_path); + } + + task = g_task_new (probe, NULL, callback, user_data); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ + return MM_BASE_MODEM (mm_broadband_modem_hso_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +static gboolean +grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + const gchar *subsys; + MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE; + MMPortType port_type; + + subsys = mm_port_probe_get_port_subsys (probe); + port_type = mm_port_probe_get_port_type (probe); + + /* Detect AT port types */ + if (g_str_equal (subsys, "tty")) { + if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_CONTROL)) + pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY; + else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_APP)) + pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY; + else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_GPS_CONTROL)) + pflags = MM_PORT_SERIAL_AT_FLAG_GPS_CONTROL; + else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_MODEM)) + pflags = MM_PORT_SERIAL_AT_FLAG_PPP; + else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_GPS)) { + /* Not an AT port, but the port to grab GPS traces */ + g_assert (port_type == MM_PORT_TYPE_UNKNOWN); + port_type = MM_PORT_TYPE_GPS; + } + } + + return mm_base_modem_grab_port (modem, + mm_port_probe_peek_port (probe), + port_type, + pflags, + error); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", NULL }; + static const gchar *drivers[] = { "hso", NULL }; + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (hso_custom_init), + .finish = G_CALLBACK (hso_custom_init_finish), + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_HSO, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_DRIVERS, drivers, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QCDM, TRUE, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + MM_PLUGIN_SEND_DELAY, (guint64) 0, + NULL)); +} + +static void +mm_plugin_hso_init (MMPluginHso *self) +{ +} + +static void +mm_plugin_hso_class_init (MMPluginHsoClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = grab_port; +} diff --git a/src/plugins/option/mm-plugin-hso.h b/src/plugins/option/mm-plugin-hso.h new file mode 100644 index 00000000..5ef13439 --- /dev/null +++ b/src/plugins/option/mm-plugin-hso.h @@ -0,0 +1,42 @@ +/* -*- 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 hso) 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_HSO_H +#define MM_PLUGIN_HSO_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_HSO (mm_plugin_hso_get_type ()) +#define MM_PLUGIN_HSO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_HSO, MMPluginHso)) +#define MM_PLUGIN_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_HSO, MMPluginHsoClass)) +#define MM_IS_PLUGIN_HSO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_HSO)) +#define MM_IS_PLUGIN_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_HSO)) +#define MM_PLUGIN_HSO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_HSO, MMPluginHsoClass)) + +typedef struct { + MMPlugin parent; +} MMPluginHso; + +typedef struct { + MMPluginClass parent; +} MMPluginHsoClass; + +GType mm_plugin_hso_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_HSO_H */ diff --git a/src/plugins/option/mm-plugin-option.c b/src/plugins/option/mm-plugin-option.c new file mode 100644 index 00000000..4dcb55a1 --- /dev/null +++ b/src/plugins/option/mm-plugin-option.c @@ -0,0 +1,121 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-private-boxed-types.h" +#include "mm-plugin-option.h" +#include "mm-broadband-modem-option.h" + +G_DEFINE_TYPE (MMPluginOption, mm_plugin_option, 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) +{ + return MM_BASE_MODEM (mm_broadband_modem_option_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +static gboolean +grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE; + MMKernelDevice *port; + gint usbif; + + /* The Option plugin cannot do anything with non-AT ports */ + if (!mm_port_probe_is_at (probe)) { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Ignoring non-AT port"); + return FALSE; + } + + port = mm_port_probe_peek_port (probe); + + /* Genuine Option NV devices are always supposed to use USB interface 0 as + * the modem/data port, per mail with Option engineers. Only this port + * will emit responses to dialing commands. + */ + usbif = mm_kernel_device_get_interface_number (port); + if (usbif == 0) + pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY | MM_PORT_SERIAL_AT_FLAG_PPP; + + return mm_base_modem_grab_port (modem, + port, + MM_PORT_TYPE_AT, /* we only allow AT ports here */ + pflags, + error); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", NULL }; + static const guint16 vendor_ids[] = { 0x0af0, /* Option USB devices */ + 0x1931, /* Nozomi CardBus devices */ + 0 }; + static const gchar *drivers[] = { "option1", "option", "nozomi", NULL }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_OPTION, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_DRIVERS, drivers, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + NULL)); +} + +static void +mm_plugin_option_init (MMPluginOption *self) +{ +} + +static void +mm_plugin_option_class_init (MMPluginOptionClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = grab_port; +} diff --git a/src/plugins/option/mm-plugin-option.h b/src/plugins/option/mm-plugin-option.h new file mode 100644 index 00000000..275fc403 --- /dev/null +++ b/src/plugins/option/mm-plugin-option.h @@ -0,0 +1,42 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_OPTION_H +#define MM_PLUGIN_OPTION_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_OPTION (mm_plugin_option_get_type ()) +#define MM_PLUGIN_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_OPTION, MMPluginOption)) +#define MM_PLUGIN_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_OPTION, MMPluginOptionClass)) +#define MM_IS_PLUGIN_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_OPTION)) +#define MM_IS_PLUGIN_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_OPTION)) +#define MM_PLUGIN_OPTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_OPTION, MMPluginOptionClass)) + +typedef struct { + MMPlugin parent; +} MMPluginOption; + +typedef struct { + MMPluginClass parent; +} MMPluginOptionClass; + +GType mm_plugin_option_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_OPTION_H */ diff --git a/src/plugins/option/mm-shared-option.c b/src/plugins/option/mm-shared-option.c new file mode 100644 index 00000000..a06888a1 --- /dev/null +++ b/src/plugins/option/mm-shared-option.c @@ -0,0 +1,77 @@ +/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <stdio.h> + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-iface-modem.h" +#include "mm-sim-option.h" +#include "mm-shared-option.h" + +/*****************************************************************************/ +/* Create SIM (Modem inteface) */ + +MMBaseSim * +mm_shared_option_create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_option_new_finish (res, error); +} + +void +mm_shared_option_create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_sim_option_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + +/*****************************************************************************/ + +static void +shared_option_init (gpointer g_iface) +{ +} + +GType +mm_shared_option_get_type (void) +{ + static GType shared_option_type = 0; + + if (!G_UNLIKELY (shared_option_type)) { + static const GTypeInfo info = { + sizeof (MMSharedOption), /* class_size */ + shared_option_init, /* base_init */ + NULL, /* base_finalize */ + }; + + shared_option_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedOption", &info, 0); + g_type_interface_add_prerequisite (shared_option_type, MM_TYPE_IFACE_MODEM); + } + + return shared_option_type; +} diff --git a/src/plugins/option/mm-shared-option.h b/src/plugins/option/mm-shared-option.h new file mode 100644 index 00000000..0d4baf60 --- /dev/null +++ b/src/plugins/option/mm-shared-option.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: + * + * Copyright (C) 2021 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_SHARED_OPTION_H +#define MM_SHARED_OPTION_H + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" + +#define MM_TYPE_SHARED_OPTION (mm_shared_option_get_type ()) +#define MM_SHARED_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_OPTION, MMSharedOption)) +#define MM_IS_SHARED_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_OPTION)) +#define MM_SHARED_OPTION_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_OPTION, MMSharedOption)) + +typedef struct _MMSharedOption MMSharedOption; + +struct _MMSharedOption { + GTypeInterface g_iface; +}; + +GType mm_shared_option_get_type (void); + +void mm_shared_option_create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_shared_option_create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +#endif /* MM_SHARED_OPTION_H */ diff --git a/src/plugins/option/mm-shared.c b/src/plugins/option/mm-shared.c new file mode 100644 index 00000000..3f89d86a --- /dev/null +++ b/src/plugins/option/mm-shared.c @@ -0,0 +1,20 @@ +/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-shared.h" + +MM_SHARED_DEFINE_MAJOR_VERSION +MM_SHARED_DEFINE_MINOR_VERSION +MM_SHARED_DEFINE_NAME(Option) diff --git a/src/plugins/option/mm-sim-option.c b/src/plugins/option/mm-sim-option.c new file mode 100644 index 00000000..0871c4f2 --- /dev/null +++ b/src/plugins/option/mm-sim-option.c @@ -0,0 +1,84 @@ +/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es> + */ + +#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-option.h" + +G_DEFINE_TYPE (MMSimOption, mm_sim_option, MM_TYPE_BASE_SIM) + +/*****************************************************************************/ + +MMBaseSim * +mm_sim_option_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_option_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (MM_TYPE_SIM_OPTION, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_SIM_MODEM, modem, + "active", TRUE, /* by default always active */ + NULL); +} + +static void +mm_sim_option_init (MMSimOption *self) +{ +} + +static void +mm_sim_option_class_init (MMSimOptionClass *klass) +{ + MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass); + + /* Skip managing preferred networks, not supported by Option modems */ + base_sim_class->load_preferred_networks = NULL; + base_sim_class->load_preferred_networks_finish = NULL; + base_sim_class->set_preferred_networks = NULL; + base_sim_class->set_preferred_networks_finish = NULL; +} diff --git a/src/plugins/option/mm-sim-option.h b/src/plugins/option/mm-sim-option.h new file mode 100644 index 00000000..c502a397 --- /dev/null +++ b/src/plugins/option/mm-sim-option.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) 2021 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_SIM_OPTION_H +#define MM_SIM_OPTION_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-sim.h" + +#define MM_TYPE_SIM_OPTION (mm_sim_option_get_type ()) +#define MM_SIM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_OPTION, MMSimOption)) +#define MM_SIM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_OPTION, MMSimOptionClass)) +#define MM_IS_SIM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_OPTION)) +#define MM_IS_SIM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_OPTION)) +#define MM_SIM_OPTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_OPTION, MMSimOptionClass)) + +typedef struct _MMSimOption MMSimOption; +typedef struct _MMSimOptionClass MMSimOptionClass; + +struct _MMSimOption { + MMBaseSim parent; +}; + +struct _MMSimOptionClass { + MMBaseSimClass parent; +}; + +GType mm_sim_option_get_type (void); + +void mm_sim_option_new (MMBaseModem *modem, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseSim *mm_sim_option_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_SIM_OPTION_H */ |