diff options
author | Matthew Stanger <matthew_stanger@trimble.com> | 2016-10-23 19:28:19 -0600 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2016-11-23 22:48:29 +0100 |
commit | ffe980b164437489e095976a348522c6c904e43d (patch) | |
tree | e5a16eada0796c340807da80f8bafe8b90b8d26a | |
parent | 905c8a48e96350af002536b627b46fbf517f727c (diff) | |
download | ModemManager-ffe980b164437489e095976a348522c6c904e43d.tar.gz |
cinterion: implement support for the new SWWAN interface
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | plugins/Makefile.am | 2 | ||||
-rw-r--r-- | plugins/cinterion/mm-broadband-bearer-cinterion.c | 902 | ||||
-rw-r--r-- | plugins/cinterion/mm-broadband-bearer-cinterion.h | 57 | ||||
-rw-r--r-- | plugins/cinterion/mm-broadband-modem-cinterion.c | 229 | ||||
-rw-r--r-- | plugins/cinterion/mm-modem-helpers-cinterion.c | 81 | ||||
-rw-r--r-- | plugins/cinterion/mm-modem-helpers-cinterion.h | 7 | ||||
-rw-r--r-- | plugins/cinterion/tests/test-modem-helpers-cinterion.c | 135 | ||||
-rw-r--r-- | src/mm-base-modem.c | 20 | ||||
-rw-r--r-- | src/mm-base-modem.h | 1 |
10 files changed, 1434 insertions, 1 deletions
@@ -69,3 +69,4 @@ Other contributors: Vitaly Gimly Yuri Chornoivan kuonirat + Matthew Stanger diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 070e379e3..86026fd57 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -646,6 +646,8 @@ libmm_plugin_cinterion_la_SOURCES = \ cinterion/mm-common-cinterion.h \ cinterion/mm-broadband-modem-cinterion.c \ cinterion/mm-broadband-modem-cinterion.h \ + cinterion/mm-broadband-bearer-cinterion.c \ + cinterion/mm-broadband-bearer-cinterion.h \ $(NULL) if WITH_QMI libmm_plugin_cinterion_la_SOURCES += \ diff --git a/plugins/cinterion/mm-broadband-bearer-cinterion.c b/plugins/cinterion/mm-broadband-bearer-cinterion.c new file mode 100644 index 000000000..df8831972 --- /dev/null +++ b/plugins/cinterion/mm-broadband-bearer-cinterion.c @@ -0,0 +1,902 @@ +/* -*- 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) 2016 Trimble Navigation Limited + * Author: Matthew Stanger <matthew_stanger@trimble.com> + */ + +#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> +#include "mm-base-modem-at.h" +#include "mm-broadband-bearer-cinterion.h" +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-cinterion.h" +#include "mm-daemon-enums-types.h" + +G_DEFINE_TYPE (MMBroadbandBearerCinterion, mm_broadband_bearer_cinterion, MM_TYPE_BROADBAND_BEARER) + +/*****************************************************************************/ +/* Common enums and structs */ + +#define FIRST_USB_INTERFACE 1 +#define SECOND_USB_INTERFACE 2 + +typedef enum { + BearerCinterionAuthUnknown = -1, + BearerCinterionAuthNone = 0, + BearerCinterionAuthPap = 1, + BearerCinterionAuthChap = 2, + BearerCinterionAuthMsChapV2 = 3, +} BearerCinterionAuthType; + +typedef enum { + Connect3gppContextStepInit = 0, + Connect3gppContextStepAuth, + Connect3gppContextStepPdpCtx, + Connect3gppContextStepStartSwwan, + Connect3gppContextStepValidateConnection, + Connect3gppContextStepIpConfig, + Connect3gppContextStepFinalizeBearer, +} Connect3gppContextStep; + +typedef enum { + Disconnect3gppContextStepStopSwwan = 0, + Disconnect3gppContextStepConnectionStatus, + Disconnect3gppContextStepFinish +} Disconnect3gppContextStep; + +typedef struct { + MMBroadbandBearerCinterion *self; + MMBaseModem *modem; + MMPortSerialAt *primary; + MMPort *data; + Connect3gppContextStep connect; + MMBearerIpConfig *ipv4_config; + GCancellable *cancellable; + GSimpleAsyncResult *result; +} Connect3gppContext; + +typedef struct { + MMBroadbandBearerCinterion *self; + MMBaseModem *modem; + MMPortSerialAt *primary; + MMPort *data; + Disconnect3gppContextStep disconnect; + GSimpleAsyncResult *result; +} Disconnect3gppContext; + +struct _MMBroadbandBearerCinterionPrivate { + guint network_disconnect_pending_id;/* Flag for network-initiated disconnect */ + guint pdp_cid;/* Mapping for PDP Context to network (SWWAN) interface */ +}; + +/*****************************************************************************/ +/* Common 3GPP Function Declarations */ +static void connect_3gpp_context_step (Connect3gppContext *ctx); +static void disconnect_3gpp_context_step (Disconnect3gppContext *ctx); +static void connect_3gpp_context_complete_and_free (Connect3gppContext *ctx); +static void disconnect_3gpp_context_complete_and_free (Disconnect3gppContext *ctx); + +/*****************************************************************************/ +/* Common - Helper Functions*/ + +static void +pdp_cid_map (MMBroadbandBearerCinterion *self, const gchar *bearer_interface) +{ + /* Map PDP context from the current Bearer. USB0 -> 3rd context, USB1 -> 1st context. + * Note - Cinterion told me specifically to map the contexts to the interfaces this way, though + * I've seen that SWWAN appear's to work fine with any PDP to any interface */ + + if (g_strcmp0 (bearer_interface, "0a") == 0) + self->priv->pdp_cid = 3; + else if (g_strcmp0 (bearer_interface, "0c") == 0) + self->priv->pdp_cid = 1; + else + /* Shouldn't be able to create a bearer for SWWAN and not be able to + * find the net interface. Otherwise connects/disconnects will get wrecked */ + g_assert_not_reached (); +} + +static gint +verify_connection_state_from_swwan_response (GList *result, GError **error) +{ + /* Returns 0 if SWWAN is connected, 1 if not connected, -1 on error + * for the bearer's target interface */ + + if (g_list_length(result) != 0) { + int first_result = GPOINTER_TO_INT(result->data); + + /* Received an 'OK'(0) response */ + if (first_result == 0) + return 1; + /* 1 || 3 result is the CID, given when that context is activated. + * TODO: Rework for dual sim connections. */ + else if (first_result == 1 || first_result ==3) + return 0; + else { + for (; result; result = g_list_next(result)) + mm_err ("Unknown SWWAN response data:%i", GPOINTER_TO_INT(result->data)); + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_INVALID_ARGS, + "Unparsable SWWAN response format."); + + return -1; + } + } + else { + mm_err ("Unable to parse zero length SWWAN response."); + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_INVALID_ARGS, + "Unparsable SWWAN response format."); + + return -1; + } +} + + +/*****************************************************************************/ +/* Connect - Helper Functions*/ + +static gint +cinterion_parse_auth_type (MMBearerAllowedAuth mm_auth) +{ + switch (mm_auth) { + case MM_BEARER_ALLOWED_AUTH_NONE: + return BearerCinterionAuthNone; + case MM_BEARER_ALLOWED_AUTH_PAP: + return BearerCinterionAuthPap; + case MM_BEARER_ALLOWED_AUTH_CHAP: + return BearerCinterionAuthChap; + case MM_BEARER_ALLOWED_AUTH_MSCHAPV2: + return BearerCinterionAuthMsChapV2; + default: + return BearerCinterionAuthUnknown; + } +} + +static void +connect_3gpp_context_complete_and_free (Connect3gppContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->cancellable); + g_object_unref (ctx->result); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_clear_object (&ctx->ipv4_config); + g_clear_object (&ctx->data); + g_clear_object (&ctx->primary); + g_slice_free (Connect3gppContext, ctx); +} + +static MMBearerConnectResult * +connect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + return mm_bearer_connect_result_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); +} + + + +static void +pdp_cid_connect (Connect3gppContext *ctx) +{ + const gchar *bearer_interface; + + /* For E: INTERFACE=usb0 -> E: ID_USB_INTERFACE_NUM=0a + * For E: INTERFACE=usb1 -> E: ID_USB_INTERFACE_NUM=0c */ + + /* Look up the net port to associate with this bearer */ + bearer_interface = mm_kernel_device_get_property (mm_port_peek_kernel_device (ctx->data), + "ID_USB_INTERFACE_NUM"); + + pdp_cid_map (ctx->self, bearer_interface); + + return; +} + +static void +get_cmd_write_response_ctx_connect (MMBaseModem *modem, + GAsyncResult *res, + Connect3gppContext *ctx) +{ + /* Only use this to parse responses that respond 'Ok' or 'ERROR' */ + GError *error = NULL; + + /* Check to see if the command had an error */ + mm_base_modem_at_command_finish (modem, res, &error); + + /* We expect either OK or an error */ + if (error != NULL) { + g_simple_async_result_take_error (ctx->result, error); + connect_3gpp_context_complete_and_free (ctx); + return; + } + + g_clear_error (&error); + + /*GOTO next step */ + ctx->connect++; + connect_3gpp_context_step (ctx); +} + +static void +get_swwan_read_response_ctx_connect (MMBaseModem *modem, + GAsyncResult *res, + Connect3gppContext *ctx) +{ + const gchar *response; + GError *error = NULL; + GList *response_parsed = NULL; + + /* Get the SWWAN read response */ + response = mm_base_modem_at_command_finish (modem, res, &error); + + /* Error if parsing SWWAN read response fails */ + if (!mm_cinterion_parse_swwan_response (response, &response_parsed, &error)) { + g_simple_async_result_take_error (ctx->result, error); + connect_3gpp_context_complete_and_free (ctx); + return; + } + + /*Check parsed SWWAN reponse to see if we are now connected */ + if (verify_connection_state_from_swwan_response(response_parsed, &error)) { + g_simple_async_result_take_error (ctx->result, error); + connect_3gpp_context_complete_and_free (ctx); + return; + } + + g_list_free(response_parsed); + g_clear_error (&error); + + /* GOTO next step */ + ctx->connect++; + connect_3gpp_context_step (ctx); +} + +static void +setup_ip_settings (Connect3gppContext *ctx) +{ + MMBearerIpFamily ip_family; + + ip_family = mm_bearer_properties_get_ip_type (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self))); + + if (ip_family == MM_BEARER_IP_FAMILY_NONE || + ip_family == MM_BEARER_IP_FAMILY_ANY) { + gchar *ip_family_str; + + ip_family = mm_base_bearer_get_default_ip_family (MM_BASE_BEARER (ctx->self)); + ip_family_str = mm_bearer_ip_family_build_string_from_mask (ip_family); + mm_dbg ("No specific IP family requested, defaulting to %s", + ip_family_str); + g_free (ip_family_str); + } + + /* Default to automatic/DHCP addressing */ + ctx->ipv4_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ctx->ipv4_config, MM_BEARER_IP_METHOD_DHCP); +} + +static gchar * +build_cinterion_auth_string (Connect3gppContext *ctx) +{ + const gchar *user = NULL; + const gchar *passwd = NULL; + MMBearerAllowedAuth auth; + gint encoded_auth = BearerCinterionAuthUnknown; + gchar *command = NULL; + + user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self))); + passwd = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self))); + + /* Normal use case is no user & pass so return as quick as possible */ + if (!user && !passwd) + return NULL; + + auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self))); + encoded_auth = cinterion_parse_auth_type (auth); + + /* Default to no authentication if not specified */ + if (encoded_auth == BearerCinterionAuthUnknown) { + encoded_auth = BearerCinterionAuthNone; + mm_dbg ("Unable to detect authentication type. Defaulting to:%i", encoded_auth); + } + + /* TODO: Haven't tested this as I can't get a hold of a SIM w/ this feature atm. + * Write Command + * AT^SGAUTH=<cid>[, <auth_type>[, <passwd>, <user>]] + * Response(s) + * OK + * ERROR + * +CME ERROR: <err> + */ + + command = g_strdup_printf ("^SGAUTH=%i,%i,%s,%s", + ctx->self->priv->pdp_cid, + encoded_auth, + passwd, + user); + + return command; +} + +static gchar * +build_cinterion_pdp_context_string (Connect3gppContext *ctx) +{ + const gchar *apn = NULL; + gchar *command; + + apn = mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self))); + + /* TODO: Get IP type if protocol was specified. Hardcoded to IPV4 for now */ + + command = g_strdup_printf ("+CGDCONT=%i,\"IP\",\"%s\"", + ctx->self->priv->pdp_cid, + apn == NULL ? "" : apn); + + return command; +} + +static void +handle_cancel_connect (Connect3gppContext *ctx) +{ + gchar *command; + + /* 3rd context -> 1st wwan adapt / 1st context -> 2nd wwan adapt */ + command = g_strdup_printf ("^SWWAN=%s,%i,%i", + "0", + ctx->self->priv->pdp_cid, + ctx->self->priv->pdp_cid == 3 ? FIRST_USB_INTERFACE : SECOND_USB_INTERFACE); + + /* Disconnect, may not succeed. Will not check response on cancel */ + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + FALSE, + NULL, + NULL, + NULL); + + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_CANCELLED, + "Cinterion connection operation has been cancelled"); + connect_3gpp_context_complete_and_free (ctx); + +} + +static void +create_cinterion_bearer (Connect3gppContext *ctx) +{ + if (ctx->ipv4_config) { + g_simple_async_result_set_op_res_gpointer ( + ctx->result, + mm_bearer_connect_result_new (ctx->data, ctx->ipv4_config, NULL), + (GDestroyNotify)mm_bearer_connect_result_unref); + } + else { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_WRONG_STATE, + "Cinterion connection failed to set IP protocol"); + } +} + +/*****************************************************************************/ +/* Connect - AT Command Wrappers*/ + +static void +send_swwan_read_command_ctx_connect (Connect3gppContext *ctx) +{ + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "^SWWAN?", + 5, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)get_swwan_read_response_ctx_connect, + ctx); +} + + +static void +send_write_command_ctx_connect (Connect3gppContext *ctx, + gchar **command) +{ + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + *command, + 10,/*Seen it take 5 seconds :0 */ + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)get_cmd_write_response_ctx_connect, + ctx); +} + +static void +send_swwan_connect_command_ctx_connect (Connect3gppContext *ctx) +{ + /* 3rd context -> 1st wwan adapt / 1st context -> 2nd wwan adapt */ + gchar *command; + command = g_strdup_printf ("^SWWAN=%s,%i,%i", + "1", + ctx->self->priv->pdp_cid, + ctx->self->priv->pdp_cid == 3 ? FIRST_USB_INTERFACE : SECOND_USB_INTERFACE); + + send_write_command_ctx_connect(ctx, &command); + + g_free (command); +} + +static void +send_swwan_disconnect_command_ctx_connect (Connect3gppContext *ctx) +{ + /* 3rd context -> 1st wwan adapt / 1st context -> 2nd wwan adapt */ + gchar *command; + command = g_strdup_printf ("^SWWAN=%s,%i,%i", + "0", + ctx->self->priv->pdp_cid, + ctx->self->priv->pdp_cid == 3 ? FIRST_USB_INTERFACE : SECOND_USB_INTERFACE); + + send_write_command_ctx_connect(ctx, &command); + + g_free (command); +} + +/*****************************************************************************/ +/* Connect - Bearer */ + +static void +connect_3gpp_context_step (Connect3gppContext *ctx) +{ + /* Check for cancellation */ + if (g_cancellable_is_cancelled (ctx->cancellable)) { + handle_cancel_connect(ctx); + return; + } + + mm_dbg ("Connect Step:%i", ctx->connect); + + /* Network-initiated disconnect should not be outstanding at this point, + because it interferes with the connect attempt.*/ + g_assert (ctx->self->priv->network_disconnect_pending_id == 0); + + switch (ctx->connect) { + case Connect3gppContextStepInit: + + /* Insure no connection is currently + * active with the bearer we're creating.*/ + send_swwan_disconnect_command_ctx_connect (ctx); + + return; + case Connect3gppContextStepAuth: { + + gchar *command = NULL; + + command = build_cinterion_auth_string (ctx); + + mm_dbg ("Auth String:%s", command); + + /* Send SGAUTH write, if User & Pass are provided. + * advance to next state by callback */ + if (command != NULL) { + mm_dbg ("Sending auth"); + + send_write_command_ctx_connect (ctx, &command); + g_free (command); + return; + } + + /* GOTO next step - Fall down below */ + ctx->connect++; + } + case Connect3gppContextStepPdpCtx: { + gchar *command = NULL; + + command = build_cinterion_pdp_context_string (ctx); + + /*Set the PDP context with cgdcont.*/ + send_write_command_ctx_connect (ctx, &command); + + g_free (command); + return; + } + case Connect3gppContextStepStartSwwan: + + send_swwan_connect_command_ctx_connect (ctx); + + return; + case Connect3gppContextStepValidateConnection: + + send_swwan_read_command_ctx_connect (ctx); + + return; + case Connect3gppContextStepIpConfig: + + setup_ip_settings (ctx); + + /* GOTO next step - Fall down below */ + ctx->connect++; + case Connect3gppContextStepFinalizeBearer: + /* Setup bearer */ + create_cinterion_bearer (ctx); + + /* Clear context */ + ctx->self->priv->pdp_cid = 0; + + connect_3gpp_context_complete_and_free (ctx); + return; + } +} + +static void +connect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Connect3gppContext *ctx; + MMPort *port; + + g_assert (primary != NULL); + + /* Get a Net port to setup the connection on */ + port = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET); + if (!port) { + g_simple_async_report_error_in_idle (G_OBJECT (self), + callback, + user_data, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to launch connection"); + return; + } + + /* Setup connection context */ + ctx = g_slice_new0 (Connect3gppContext); + ctx->self = g_object_ref (self); + ctx->modem = g_object_ref (modem); + ctx->data = g_object_ref (port); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + connect_3gpp); + ctx->cancellable = g_object_ref (cancellable); + ctx->primary = g_object_ref (primary); + + /* Maps Bearer-> Net Interface-> PDP Context */ + pdp_cid_connect (ctx); + + /* Initialize */ + ctx->connect = Connect3gppContextStepInit; + + /* Run! */ + connect_3gpp_context_step (ctx); +} + + +/*****************************************************************************/ +/* Disconnect - Helper Functions*/ + +static void +pdp_cid_disconnect(Disconnect3gppContext *ctx) +{ + const gchar *bearer_interface; + + /* For E: INTERFACE=usb0 -> E: ID_USB_INTERFACE_NUM=0a + * For E: INTERFACE=usb1 -> E: ID_USB_INTERFACE_NUM=0c */ + + /* Look up the net port to associate with this bearer */ + bearer_interface = mm_kernel_device_get_property (mm_port_peek_kernel_device (ctx->data), + "ID_USB_INTERFACE_NUM"); + + pdp_cid_map (ctx->self, bearer_interface); + + return; +} + +static void +get_cmd_write_response_ctx_disconnect (MMBaseModem *modem, + GAsyncResult *res, + Disconnect3gppContext *ctx) +{ + /* We don't bother to check error or response here since, ctx flow's + * next step checks it */ + + /* GOTO next step */ + ctx->disconnect++; + disconnect_3gpp_context_step (ctx); +} + +static void +get_swwan_read_response_ctx_disconnect (MMBaseModem *modem, + GAsyncResult *res, + Disconnect3gppContext *ctx) +{ + const gchar *response; + GError *error = NULL; + GList *response_parsed = NULL; + + /* Get the SWWAN response */ + response = mm_base_modem_at_command_finish (modem, res, &error); + + /* Return if the SWWAN read threw an error or parsing it fails */ + if (!mm_cinterion_parse_swwan_response (response, &response_parsed, &error)) { + g_simple_async_result_take_error (ctx->result, error); + disconnect_3gpp_context_complete_and_free (ctx); + return; + } + + /* Check parsed SWWAN reponse to see if we are now disconnected */ + if (verify_connection_state_from_swwan_response (response_parsed, &error) != 1) { + + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Disconnection attempt failed"); + + disconnect_3gpp_context_complete_and_free (ctx); + return; + } + + g_list_free (response_parsed); + g_clear_error (&error); + + /* GOTO next step */ + ctx->disconnect++; + disconnect_3gpp_context_step (ctx); +} + +/*****************************************************************************/ +/* Disconnect - AT Command Wrappers*/ + +static void +send_swwan_disconnect_command_ctx_disconnect (Disconnect3gppContext *ctx) +{ + /* 3rd context -> 1st wwan adapt / 1st context -> 2nd wwan adapt */ + gchar *command; + command = g_strdup_printf ("^SWWAN=%s,%i,%i", + "0", + ctx->self->priv->pdp_cid, + ctx->self->priv->pdp_cid == 3 ? FIRST_USB_INTERFACE : SECOND_USB_INTERFACE); + + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 10,/*Seen it take 5 seconds :0 */ + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)get_cmd_write_response_ctx_disconnect, + ctx); + + g_free (command); +} + +static void +send_swwan_read_command_ctx_disconnect (Disconnect3gppContext *ctx) +{ + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "^SWWAN?", + 5, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)get_swwan_read_response_ctx_disconnect, + ctx); +} + +/*****************************************************************************/ +/* Disconnect - Bearer */ + +static void +disconnect_3gpp_context_complete_and_free (Disconnect3gppContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_clear_object (&ctx->data); + g_object_unref (ctx->result); + g_object_unref (ctx->primary); + g_object_unref (ctx->self); + g_object_unref (ctx->modem); + g_slice_free (Disconnect3gppContext, ctx); +} + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +disconnect_3gpp_context_step (Disconnect3gppContext *ctx) +{ + mm_dbg ("Disconnect Step:%i", ctx->disconnect); + + switch (ctx->disconnect) { + case Disconnect3gppContextStepStopSwwan: + + /* Has call back to next state */ + send_swwan_disconnect_command_ctx_disconnect (ctx); + + return; + case Disconnect3gppContextStepConnectionStatus: + + /* Has call back to next state */ + send_swwan_read_command_ctx_disconnect (ctx); + + return; + + case Disconnect3gppContextStepFinish: + + ctx->self->priv->pdp_cid = 0; + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + disconnect_3gpp_context_complete_and_free (ctx); + + return; + } +} + +static void +disconnect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Disconnect3gppContext *ctx; + MMPort *port; + + g_assert (primary != NULL); + + /* Note: Not sure how else to get active data port? Can this be done without adding this + * function to mm-base-modem.c? + * TODO: Dual SIM how do we know which interface to grab/disconnect if two are active? */ + /* Get the Net port to be torn down */ + port = mm_base_modem_peek_current_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET); + if (!port) { + g_simple_async_report_error_in_idle (G_OBJECT (self), + callback, + user_data, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to tear down."); + return; + } + + /* Setup connection context */ + ctx = g_slice_new0 (Disconnect3gppContext); + ctx->self = g_object_ref (self); + ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + disconnect_3gpp); + ctx->data = g_object_ref (port); + ctx->primary = g_object_ref (primary); + + /* Maps Bearer->Net Interface-> PDP Context + * We can't disconnect if we don't know which context to disconnect from. */ + pdp_cid_disconnect (ctx); + + /* Initialize */ + ctx->disconnect = Disconnect3gppContextStepStopSwwan; + + /* Start */ + disconnect_3gpp_context_step (ctx); +} + +/*****************************************************************************/ +/* Setup and Init Bearers */ + +MMBaseBearer * +mm_broadband_bearer_cinterion_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); +} + +static void +dispose (GObject *object) +{ + MMBroadbandBearerCinterion *self = MM_BROADBAND_BEARER_CINTERION (object); + + if (self->priv->network_disconnect_pending_id != 0) { + g_source_remove (self->priv->network_disconnect_pending_id); + self->priv->network_disconnect_pending_id = 0; + } + + G_OBJECT_CLASS (mm_broadband_bearer_cinterion_parent_class)->dispose (object); +} + +void +mm_broadband_bearer_cinterion_new (MMBroadbandModemCinterion *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_CINTERION, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BASE_BEARER_CONFIG, config, + NULL); +} + +static void +mm_broadband_bearer_cinterion_init (MMBroadbandBearerCinterion *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_BEARER_CINTERION, + MMBroadbandBearerCinterionPrivate); +} + +static void +mm_broadband_bearer_cinterion_class_init (MMBroadbandBearerCinterionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandBearerCinterionPrivate)); + + object_class->dispose = dispose; + + broadband_bearer_class->connect_3gpp = connect_3gpp; + broadband_bearer_class->connect_3gpp_finish = connect_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; +} diff --git a/plugins/cinterion/mm-broadband-bearer-cinterion.h b/plugins/cinterion/mm-broadband-bearer-cinterion.h new file mode 100644 index 000000000..01d360599 --- /dev/null +++ b/plugins/cinterion/mm-broadband-bearer-cinterion.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2016 Trimble Navigation Limited + * Author: Matthew Stanger <Matthew_Stanger@trimble.com> + */ + +#ifndef MM_BROADBAND_BEARER_CINTERION_H +#define MM_BROADBAND_BEARER_CINTERION_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-cinterion.h" + +#define MM_TYPE_BROADBAND_BEARER_CINTERION (mm_broadband_bearer_cinterion_get_type ()) +#define MM_BROADBAND_BEARER_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterion)) +#define MM_BROADBAND_BEARER_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterionClass)) +#define MM_IS_BROADBAND_BEARER_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION)) +#define MM_IS_BROADBAND_BEARER_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_CINTERION)) +#define MM_BROADBAND_BEARER_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterionClass)) + +typedef struct _MMBroadbandBearerCinterion MMBroadbandBearerCinterion; +typedef struct _MMBroadbandBearerCinterionClass MMBroadbandBearerCinterionClass; +typedef struct _MMBroadbandBearerCinterionPrivate MMBroadbandBearerCinterionPrivate; + +struct _MMBroadbandBearerCinterion { + MMBroadbandBearer parent; + MMBroadbandBearerCinterionPrivate *priv; +}; + +struct _MMBroadbandBearerCinterionClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_cinterion_get_type (void); + +void mm_broadband_bearer_cinterion_new (MMBroadbandModemCinterion *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +MMBaseBearer *mm_broadband_bearer_cinterion_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_BROADBAND_BEARER_CINTERION_H */ diff --git a/plugins/cinterion/mm-broadband-modem-cinterion.c b/plugins/cinterion/mm-broadband-modem-cinterion.c index 4882a4188..28bdc404f 100644 --- a/plugins/cinterion/mm-broadband-modem-cinterion.c +++ b/plugins/cinterion/mm-broadband-modem-cinterion.c @@ -12,7 +12,9 @@ * * Copyright (C) 2011 Ammonit Measurement GmbH * Copyright (C) 2011 Google Inc. + * Copyright (C) 2016 Trimble Navigation Limited * Author: Aleksander Morgado <aleksander@lanedo.com> + * Contributor: Matthew Stanger <matthew_stanger@trimble.com> */ #include <config.h> @@ -36,6 +38,7 @@ #include "mm-broadband-modem-cinterion.h" #include "mm-modem-helpers-cinterion.h" #include "mm-common-cinterion.h" +#include "mm-broadband-bearer-cinterion.h" static void iface_modem_init (MMIfaceModem *iface); static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); @@ -50,6 +53,12 @@ G_DEFINE_TYPE_EXTENDED (MMBroadbandModemCinterion, mm_broadband_modem_cinterion, G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init) G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)) +typedef enum { + FeatureSupportUnknown, + FeatureNotSupported, + FeatureSupported +} FeatureSupport; + struct _MMBroadbandModemCinterionPrivate { /* Flag to know if we should try AT^SIND or not to get psinfo */ gboolean sind_psinfo; @@ -69,6 +78,9 @@ struct _MMBroadbandModemCinterionPrivate { GArray *cnmi_supported_bm; GArray *cnmi_supported_ds; GArray *cnmi_supported_bfr; + + /*Flags for SWWAN support*/ + FeatureSupport swwan_support; }; /*****************************************************************************/ @@ -727,10 +739,16 @@ get_access_technology_from_psinfo (const gchar *psinfo, case 9: case 10: return (MM_MODEM_ACCESS_TECHNOLOGY_HSDPA | MM_MODEM_ACCESS_TECHNOLOGY_HSUPA); + case 16: + case 17: + return MM_MODEM_ACCESS_TECHNOLOGY_LTE; default: + mm_dbg ("Unable to identify access technology in case:%i", psinfoval); break; } } + else + mm_err ("FAILED get_access_technology_from_psinfo-int"); g_set_error (error, MM_CORE_ERROR, @@ -811,6 +829,7 @@ load_access_technologies (MMIfaceModem *self, load_access_technologies); if (broadband->priv->sind_psinfo) { + /* TODO: Trigger off psinfo URC instead of this polling. */ mm_base_modem_at_command ( MM_BASE_MODEM (self), "^SIND?", @@ -1636,6 +1655,99 @@ after_sim_unlock (MMIfaceModem *self, } /*****************************************************************************/ +/* Initializing the modem (during first enabling) */ + +typedef struct { + GSimpleAsyncResult *result; + MMBroadbandModemCinterion *self; +} EnablingModemInitContext; + +static void +enabling_modem_init_context_complete_and_free (EnablingModemInitContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->self); + g_slice_free (EnablingModemInitContext, ctx); +} + +static gboolean +enabling_modem_init_finish (MMBroadbandModem *self, + GAsyncResult *res, + GError **error) + +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +swwan_test_ready (MMBaseModem *self, + GAsyncResult *res, + EnablingModemInitContext *ctx) +{ + MMPort *port = NULL; + GError *error = NULL; + + /* Fetch the result to the SWWAN test */ + mm_base_modem_at_command_full_finish (self, res, &error); + + port = mm_base_modem_peek_best_data_port (self, MM_PORT_TYPE_NET); + + /* SWWAN requires a net port & valid test response*/ + if (mm_port_get_subsys (port) == MM_PORT_SUBSYS_NET && + !error) { + mm_dbg ("SWWAN supported"); + ctx->self->priv->swwan_support = FeatureSupported; + } + else { + mm_dbg ("SWWAN unsupported"); + ctx->self->priv->swwan_support = FeatureNotSupported; + } + + if (error) /* Error is not valid */ + g_clear_error (&error); + + enabling_modem_init_context_complete_and_free (ctx); +} + +static void +check_for_swwan_support (EnablingModemInitContext *ctx) +{ + mm_base_modem_at_command_full (MM_BASE_MODEM (ctx->self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (ctx->self)), + "^SWWAN=?", + 6, + FALSE, + FALSE, + NULL, /* cancellable */ + (GAsyncReadyCallback)swwan_test_ready, + ctx); +} + +static void +enabling_modem_init (MMBroadbandModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnablingModemInitContext *ctx; + + ctx = g_slice_new0 (EnablingModemInitContext); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + enabling_modem_init); + ctx->self = g_object_ref (self); + + /* Newer Cinterion modems may support SWWAN, which is the same as WWAN. + * Check to see if current modem supports it.*/ + check_for_swwan_support (ctx); + + /* TODO: Before upstream merge. This needs check_for_swwan_support to block + * until it's finish. Otherwise there is race conidion with swwan_support assert? + * I'm really not sure how else to do this other than move it or use sleep :? */ +} + +/*****************************************************************************/ /* Setup ports (Broadband modem class) */ static void @@ -1648,6 +1760,116 @@ setup_ports (MMBroadbandModem *self) } /*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +typedef struct { + MMBroadbandModemCinterion *self; + GSimpleAsyncResult *result; + MMBearerProperties *properties; +} CreateBearerContext; + +static void +create_bearer_context_complete_and_free (CreateBearerContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->self); + g_object_unref (ctx->properties); + g_slice_free (CreateBearerContext, ctx); +} + +static MMBaseBearer * +cinterion_modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + MMBaseBearer *bearer; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + bearer = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); + mm_dbg ("New cinterion bearer created at DBus path '%s'", mm_base_bearer_get_path (bearer)); + return g_object_ref (bearer); +} + +static void +broadband_bearer_cinterion_new_ready (GObject *source, + GAsyncResult *res, + CreateBearerContext *ctx) +{ + MMBaseBearer *bearer; + GError *error = NULL; + + bearer = mm_broadband_bearer_cinterion_new_finish (res, &error); + if (!bearer) + g_simple_async_result_take_error (ctx->result, error); + else + g_simple_async_result_set_op_res_gpointer (ctx->result, bearer, (GDestroyNotify)g_object_unref); + create_bearer_context_complete_and_free (ctx); +} + +static void +broadband_bearer_new_ready (GObject *source, + GAsyncResult *res, + CreateBearerContext *ctx) +{ + MMBaseBearer *bearer; + GError *error = NULL; + + bearer = mm_broadband_bearer_new_finish (res, &error); + if (!bearer) + g_simple_async_result_take_error (ctx->result, error); + else + g_simple_async_result_set_op_res_gpointer (ctx->result, bearer, (GDestroyNotify)g_object_unref); + create_bearer_context_complete_and_free (ctx); +} + +static void +create_bearer_for_net_port (CreateBearerContext *ctx) +{ + switch (ctx->self->priv->swwan_support) { + case FeatureNotSupported: + mm_dbg ("^SWWAN not supported, creating default bearer..."); + mm_broadband_bearer_new (MM_BROADBAND_MODEM (ctx->self), + ctx->properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_new_ready, + ctx); + return; + case FeatureSupported: + mm_dbg ("^SWWAN supported, creating cinterion bearer..."); + mm_broadband_bearer_cinterion_new (MM_BROADBAND_MODEM_CINTERION (ctx->self), + ctx->properties, + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_cinterion_new_ready, + ctx); + return; + default: + g_assert_not_reached (); + } +} + +static void +cinterion_modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CreateBearerContext *ctx = NULL; + + ctx = g_slice_new0 (CreateBearerContext); + ctx->self = g_object_ref (self); + ctx->properties = g_object_ref (properties); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + cinterion_modem_create_bearer); + + create_bearer_for_net_port (ctx); +} + +/*****************************************************************************/ MMBroadbandModemCinterion * mm_broadband_modem_cinterion_new (const gchar *device, @@ -1673,8 +1895,9 @@ mm_broadband_modem_cinterion_init (MMBroadbandModemCinterion *self) MM_TYPE_BROADBAND_MODEM_CINTERION, MMBroadbandModemCinterionPrivate); - /* Set defaults */ + /* Initialize private variables */ self->priv->sind_psinfo = TRUE; /* Initially, always try to get psinfo */ + self->priv->swwan_support = FeatureSupportUnknown; } static void @@ -1704,6 +1927,8 @@ iface_modem_init (MMIfaceModem *iface) { iface_modem_parent = g_type_interface_peek_parent (iface); + iface->create_bearer = cinterion_modem_create_bearer; + iface->create_bearer_finish = cinterion_modem_create_bearer_finish; iface->load_supported_modes = load_supported_modes; iface->load_supported_modes_finish = load_supported_modes_finish; iface->set_current_modes = set_current_modes; @@ -1770,4 +1995,6 @@ mm_broadband_modem_cinterion_class_init (MMBroadbandModemCinterionClass *klass) /* Virtual methods */ object_class->finalize = finalize; broadband_modem_class->setup_ports = setup_ports; + broadband_modem_class->enabling_modem_init = enabling_modem_init; + broadband_modem_class->enabling_modem_init_finish = enabling_modem_init_finish; } diff --git a/plugins/cinterion/mm-modem-helpers-cinterion.c b/plugins/cinterion/mm-modem-helpers-cinterion.c index bffe00a9e..6312e7821 100644 --- a/plugins/cinterion/mm-modem-helpers-cinterion.c +++ b/plugins/cinterion/mm-modem-helpers-cinterion.c @@ -10,12 +10,15 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details: * + * Copyright (C) 2016 Trimble Navigation Limited * Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es> + * Contributor: Matthew Stanger <matthew_stanger@trimble.com> */ #include <config.h> #include <string.h> #include <stdlib.h> +#include <stdio.h> #include "ModemManager.h" #define _LIBMM_INSIDE_MM @@ -24,6 +27,7 @@ #include "mm-charsets.h" #include "mm-errors-types.h" #include "mm-modem-helpers-cinterion.h" +#include "mm-modem-helpers.h" /* Setup relationship between the 3G band bitmask in the modem and the bitmask * in ModemManager. */ @@ -482,3 +486,80 @@ mm_cinterion_parse_sind_response (const gchar *response, return TRUE; } + +/*****************************************************************************/ +/* ^SWWAN read parser + * Description: Parses <cid>, <state>[, <WWAN adapter>] or CME ERROR from SWWAN. + * Return: False if error occured while trying to parse SWWAN. + * Read Command + * AT^SWWAN? + * Response(s) + * [^SWWAN: <cid>, <state>[, <WWAN adapter>]] + * [^SWWAN: ...] + * OK + * ERROR + * +CME ERROR: <err> + * + * Examples: + * OK - If no WWAN connection is active, then read command just returns OK + * ^SWWAN: 3,1,1 - 3rd PDP Context, Activated, First WWAN Adaptor + * +CME ERROR: ? - +*/ + +gboolean +mm_cinterion_parse_swwan_response (const gchar *response, + GList **result, + GError **error) +{ + if (*error) + return FALSE; + + if (!response) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Recieved NULL from SWWAN response."); + return FALSE; + } + + /* Parse for [^SWWAN: <cid>, <state>[, <WWAN adapter>]] */ + if (strcasestr (response, "SWWAN")) { + gint matched; + guint cid, state, wwan_adapter; + matched = sscanf (response, "^SWWAN: %d,%d,%d", + &cid, + &state, + &wwan_adapter); + + if (matched != 3 || + cid < 1 || cid > 16 || + state < 0 || state > 1 || + wwan_adapter < 1 || wwan_adapter > 2) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid format for SWWAN response: '%s'", + response); + return FALSE; + } + + *result = g_list_append (*result, GINT_TO_POINTER(cid)); + *result = g_list_append (*result, GINT_TO_POINTER(state)); + *result = g_list_append (*result, GINT_TO_POINTER(wwan_adapter)); + } + /* TODO: It'd be nice to get 'OK' from response so we don't have to assume that + * zero length response means 'OK' or am I doing it wrong?... */ + else if (!g_utf8_strlen (response, 100)) + *result = g_list_append (*result, GINT_TO_POINTER(0)); + else { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Could not parse SWWAN response: '%s'", + response); + return FALSE; + } + + + return TRUE; +} diff --git a/plugins/cinterion/mm-modem-helpers-cinterion.h b/plugins/cinterion/mm-modem-helpers-cinterion.h index d37bcb070..c94341ec4 100644 --- a/plugins/cinterion/mm-modem-helpers-cinterion.h +++ b/plugins/cinterion/mm-modem-helpers-cinterion.h @@ -10,7 +10,9 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details: * + * Copyright (C) 2016 Trimble Navigation Limited * Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es> + * Contributor: Matthew Stanger <matthew_stanger@trimble.com> */ #ifndef MM_MODEM_HELPERS_CINTERION_H @@ -63,4 +65,9 @@ gboolean mm_cinterion_parse_sind_response (const gchar *response, guint *value, GError **error); +/*****************************************************************************/ +/* ^SWWAN response parser */ +gboolean mm_cinterion_parse_swwan_response (const gchar *response, + GList **result, + GError **error); #endif /* MM_MODEM_HELPERS_CINTERION_H */ diff --git a/plugins/cinterion/tests/test-modem-helpers-cinterion.c b/plugins/cinterion/tests/test-modem-helpers-cinterion.c index 26b07ab28..a59071e28 100644 --- a/plugins/cinterion/tests/test-modem-helpers-cinterion.c +++ b/plugins/cinterion/tests/test-modem-helpers-cinterion.c @@ -329,6 +329,140 @@ test_cnmi_phs8 (void) g_array_unref (expected_ds); g_array_unref (expected_bfr);} +static void +test_swwan_parser (const GArray *expected_cid, + const GArray *expected_state, + const GArray *expected_adapter, + const gchar *at_cmd, + const gboolean test_for_errors) +{ + GError *error = NULL; + gboolean res = TRUE; + gchar *response; + guint i, j, k; + + g_assert (expected_cid != NULL); + g_assert (expected_state != NULL); + g_assert (expected_adapter != NULL); + + /* For each expected_cid */ + for (i = 0; i < expected_cid->len; i++) { + /* For each expected_state */ + for (j = 0; j < expected_state->len; j++) { + /* For each expected_adapter */ + for (k = 0; k < expected_adapter->len; k++) { + GList *response_parsed = NULL; + + /* Build a unique at_cmd string */ + response = g_strdup_printf ("%s: %i,%i,%i\r\n\r\n", + at_cmd, + g_array_index (expected_cid, guint, i), + g_array_index (expected_state, guint, j), + g_array_index (expected_adapter, guint, k)); + + /* and send it to the parser */ + res = mm_cinterion_parse_swwan_response (response, + &response_parsed, + &error); + + if (test_for_errors) { + /* There should be errors raised */ + g_assert (res == FALSE); + g_assert (error != NULL); + + /* reset the error's everytime */ + res = TRUE; + g_clear_error (&error); + } + else { + /* The parsed values we get back should match the AT string we sent */ + g_assert (g_array_index (expected_cid, guint, i) == + GPOINTER_TO_INT(g_list_nth_data (response_parsed, 0))); + g_assert (g_array_index (expected_state, guint, j) == + GPOINTER_TO_INT(g_list_nth_data (response_parsed, 1))); + g_assert (g_array_index (expected_adapter, guint, k) == + GPOINTER_TO_INT(g_list_nth_data (response_parsed, 2))); + + /* and there should be no errors raised */ + g_assert (res == TRUE); + g_assert_no_error (error); + } + + g_list_free(response_parsed); + } + } + } +} + +static void +test_swwan_pls8 (void) +{ + GArray *good_cid; + GArray *good_state; + GArray *good_adapter; + GArray *bad_cid; + GArray *bad_state; + GArray *bad_adapter; + guint i; + guint val; + + /* AT^SWWAN=? -> '^SWWAN: (0,1),(1-16),(1,2)' */ + /* Setup array with good SWWAN values */ + good_cid = g_array_sized_new (FALSE, FALSE, sizeof (guint), 16); + for (i = 1; i < 17; i++) + val = i, g_array_append_val (good_cid, val); + + good_state = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); + val = 0, g_array_append_val (good_state, val); + val = 1, g_array_append_val (good_state, val); + + good_adapter = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); + val = 1, g_array_append_val (good_adapter, val); + val = 2, g_array_append_val (good_adapter, val); + + /* and test */ + test_swwan_parser (good_cid, + good_state, + good_adapter, + "^SWWAN", + FALSE); + + /* Setup array with bad SWWAN values */ + bad_cid = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); + val = -1, g_array_append_val (bad_cid, val); + val = 17, g_array_append_val (bad_cid, val); + + bad_state = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); + val = -1, g_array_append_val (bad_state, val); + val = 2, g_array_append_val (bad_state, val); + + bad_adapter = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); + val = -1, g_array_append_val (bad_adapter, val); + val = 0, g_array_append_val (bad_adapter, val); + val = 3, g_array_append_val (bad_adapter, val); + + /* and test */ + test_swwan_parser (bad_cid, + bad_state, + bad_adapter, + "^SWWAN", + TRUE); + + /* and again with a bad cmd */ + test_swwan_parser (bad_cid, + bad_state, + bad_adapter, + "^GARBAGE", + TRUE); + + + g_array_unref (good_cid); + g_array_unref (good_state); + g_array_unref (good_adapter); + g_array_unref (bad_cid); + g_array_unref (bad_state); + g_array_unref (bad_adapter); +} /*****************************************************************************/ /* Test ^SIND responses */ @@ -399,6 +533,7 @@ int main (int argc, char **argv) g_test_add_func ("/MM/cinterion/scfg/response/2g", test_scfg_response_2g); g_test_add_func ("/MM/cinterion/scfg/response/2g/ucs2", test_scfg_response_2g_ucs2); g_test_add_func ("/MM/cinterion/cnmi/phs8", test_cnmi_phs8); + g_test_add_func ("/MM/cinterion/swwan/pls8", test_swwan_pls8); g_test_add_func ("/MM/cinterion/sind/response/simstatus", test_sind_response_simstatus); return g_test_run (); diff --git a/src/mm-base-modem.c b/src/mm-base-modem.c index 50717471b..465ceb594 100644 --- a/src/mm-base-modem.c +++ b/src/mm-base-modem.c @@ -697,6 +697,26 @@ mm_base_modem_peek_best_data_port (MMBaseModem *self, return NULL; } +MMPort * +mm_base_modem_peek_current_data_port (MMBaseModem *self, + MMPortType type) +{ + GList *l; + + g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL); + + /* Return first connected data port */ + for (l = self->priv->data; l; l = g_list_next (l)) { + if (mm_port_get_connected ((MMPort *)l->data) && + (mm_port_get_port_type ((MMPort *)l->data) == type || + type == MM_PORT_TYPE_UNKNOWN)) { + return (MMPort *)l->data; + } + } + + return NULL; +} + GList * mm_base_modem_get_data_ports (MMBaseModem *self) { diff --git a/src/mm-base-modem.h b/src/mm-base-modem.h index db3d54d3f..6a5b88c54 100644 --- a/src/mm-base-modem.h +++ b/src/mm-base-modem.h @@ -131,6 +131,7 @@ MMPortMbim *mm_base_modem_peek_port_mbim_for_data (MMBaseModem *self, MMPo #endif MMPortSerialAt *mm_base_modem_peek_best_at_port (MMBaseModem *self, GError **error); MMPort *mm_base_modem_peek_best_data_port (MMBaseModem *self, MMPortType type); +MMPort *mm_base_modem_peek_current_data_port (MMBaseModem *self, MMPortType type); GList *mm_base_modem_peek_data_ports (MMBaseModem *self); MMPortSerialAt *mm_base_modem_get_port_primary (MMBaseModem *self); |