diff options
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/Makefile.am | 20 | ||||
-rw-r--r-- | plugins/simtech/mm-broadband-modem-qmi-simtech.c | 28 | ||||
-rw-r--r-- | plugins/simtech/mm-broadband-modem-simtech.c | 28 | ||||
-rw-r--r-- | plugins/simtech/mm-modem-helpers-simtech.c | 95 | ||||
-rw-r--r-- | plugins/simtech/mm-modem-helpers-simtech.h | 39 | ||||
-rw-r--r-- | plugins/simtech/mm-shared-simtech.c | 542 | ||||
-rw-r--r-- | plugins/simtech/mm-shared-simtech.h | 43 | ||||
-rw-r--r-- | plugins/simtech/tests/test-modem-helpers-simtech.c | 177 |
8 files changed, 968 insertions, 4 deletions
diff --git a/plugins/Makefile.am b/plugins/Makefile.am index eb63a702..ffc0c0ec 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -590,6 +590,25 @@ libmm_plugin_linktop_la_LIBADD = $(builddir)/libhelpers-linktop.la # plugin: simtech ################################################################################ +noinst_LTLIBRARIES += libhelpers-simtech.la +libhelpers_simtech_la_SOURCES = \ + simtech/mm-modem-helpers-simtech.c \ + simtech/mm-modem-helpers-simtech.h \ + $(NULL) + +noinst_PROGRAMS += test-modem-helpers-simtech +test_modem_helpers_simtech_SOURCES = \ + simtech/tests/test-modem-helpers-simtech.c \ + $(NULL) +test_modem_helpers_simtech_CPPFLAGS = \ + -I$(top_srcdir)/plugins/simtech \ + $(NULL) +test_modem_helpers_simtech_LDADD = \ + $(builddir)/libhelpers-simtech.la \ + $(top_builddir)/src/libhelpers.la \ + $(top_builddir)/libmm-glib/libmm-glib.la \ + $(NULL) + pkglib_LTLIBRARIES += libmm-plugin-simtech.la libmm_plugin_simtech_la_SOURCES = \ simtech/mm-plugin-simtech.c \ @@ -607,6 +626,7 @@ libmm_plugin_simtech_la_SOURCES += \ endif libmm_plugin_simtech_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS) libmm_plugin_simtech_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS) +libmm_plugin_simtech_la_LIBADD = $(builddir)/libhelpers-simtech.la dist_udevrules_DATA += simtech/77-mm-simtech-port-types.rules diff --git a/plugins/simtech/mm-broadband-modem-qmi-simtech.c b/plugins/simtech/mm-broadband-modem-qmi-simtech.c index c6ca6b5a..4ea84e9a 100644 --- a/plugins/simtech/mm-broadband-modem-qmi-simtech.c +++ b/plugins/simtech/mm-broadband-modem-qmi-simtech.c @@ -25,16 +25,20 @@ #include "mm-log.h" #include "mm-errors-types.h" #include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" #include "mm-broadband-modem-qmi-simtech.h" #include "mm-shared-simtech.h" static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_voice_init (MMIfaceModemVoice *iface); static void shared_simtech_init (MMSharedSimtech *iface); static MMIfaceModemLocation *iface_modem_location_parent; +static MMIfaceModemVoice *iface_modem_voice_parent; G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiSimtech, mm_broadband_modem_qmi_simtech, MM_TYPE_BROADBAND_MODEM_QMI, 0, G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init) G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_SIMTECH, shared_simtech_init)) /*****************************************************************************/ @@ -81,9 +85,33 @@ peek_parent_location_interface (MMSharedSimtech *self) } static void +iface_modem_voice_init (MMIfaceModemVoice *iface) +{ + iface_modem_voice_parent = g_type_interface_peek_parent (iface); + + iface->check_support = mm_shared_simtech_voice_check_support; + iface->check_support_finish = mm_shared_simtech_voice_check_support_finish; + iface->enable_unsolicited_events = mm_shared_simtech_voice_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = mm_shared_simtech_voice_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = mm_shared_simtech_voice_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = mm_shared_simtech_voice_disable_unsolicited_events_finish; + iface->setup_unsolicited_events = mm_shared_simtech_voice_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_simtech_voice_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_simtech_voice_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_simtech_voice_cleanup_unsolicited_events_finish; +} + +static MMIfaceModemVoice * +peek_parent_voice_interface (MMSharedSimtech *self) +{ + return iface_modem_voice_parent; +} + +static void shared_simtech_init (MMSharedSimtech *iface) { iface->peek_parent_location_interface = peek_parent_location_interface; + iface->peek_parent_voice_interface = peek_parent_voice_interface; } static void diff --git a/plugins/simtech/mm-broadband-modem-simtech.c b/plugins/simtech/mm-broadband-modem-simtech.c index 3d649a6e..6a8ae80d 100644 --- a/plugins/simtech/mm-broadband-modem-simtech.c +++ b/plugins/simtech/mm-broadband-modem-simtech.c @@ -33,22 +33,26 @@ #include "mm-iface-modem.h" #include "mm-iface-modem-3gpp.h" #include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" #include "mm-shared-simtech.h" #include "mm-broadband-modem-simtech.h" static void iface_modem_init (MMIfaceModem *iface); static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_voice_init (MMIfaceModemVoice *iface); static void shared_simtech_init (MMSharedSimtech *iface); static MMIfaceModem *iface_modem_parent; static MMIfaceModem3gpp *iface_modem_3gpp_parent; static MMIfaceModemLocation *iface_modem_location_parent; +static MMIfaceModemVoice *iface_modem_voice_parent; G_DEFINE_TYPE_EXTENDED (MMBroadbandModemSimtech, mm_broadband_modem_simtech, MM_TYPE_BROADBAND_MODEM, 0, G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init) G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_SIMTECH, shared_simtech_init)) typedef enum { @@ -1290,9 +1294,33 @@ peek_parent_location_interface (MMSharedSimtech *self) } static void +iface_modem_voice_init (MMIfaceModemVoice *iface) +{ + iface_modem_voice_parent = g_type_interface_peek_parent (iface); + + iface->check_support = mm_shared_simtech_voice_check_support; + iface->check_support_finish = mm_shared_simtech_voice_check_support_finish; + iface->enable_unsolicited_events = mm_shared_simtech_voice_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = mm_shared_simtech_voice_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = mm_shared_simtech_voice_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = mm_shared_simtech_voice_disable_unsolicited_events_finish; + iface->setup_unsolicited_events = mm_shared_simtech_voice_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_simtech_voice_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_simtech_voice_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_simtech_voice_cleanup_unsolicited_events_finish; +} + +static MMIfaceModemVoice * +peek_parent_voice_interface (MMSharedSimtech *self) +{ + return iface_modem_voice_parent; +} + +static void shared_simtech_init (MMSharedSimtech *iface) { iface->peek_parent_location_interface = peek_parent_location_interface; + iface->peek_parent_voice_interface = peek_parent_voice_interface; } static void diff --git a/plugins/simtech/mm-modem-helpers-simtech.c b/plugins/simtech/mm-modem-helpers-simtech.c new file mode 100644 index 00000000..e25bc118 --- /dev/null +++ b/plugins/simtech/mm-modem-helpers-simtech.c @@ -0,0 +1,95 @@ +/* -*- 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 <config.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "ModemManager.h" +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> +#include "mm-log.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers-simtech.h" +#include "mm-modem-helpers.h" + + +/*****************************************************************************/ +/* +CLCC test parser + * + * Example (SIM7600E): + * AT+CLCC=? + * +CLCC: (0-1) + */ + +gboolean +mm_simtech_parse_clcc_test (const gchar *response, + gboolean *clcc_urcs_supported, + GError **error) +{ + g_assert (response); + + response = mm_strip_tag (response, "+CLCC:"); + + /* 3GPP specifies that the output of AT+CLCC=? should be just OK, so support + * that */ + if (!response[0]) { + *clcc_urcs_supported = FALSE; + return TRUE; + } + + /* As per 3GPP TS 27.007, the AT+CLCC command doesn't expect any argument, + * as it only is designed to report the current call list, nothing else. + * In the case of the Simtech plugin, though, we are going to support +CLCC + * URCs that can be enabled/disabled via AT+CLCC=1/0. We therefore need to + * detect whether this URC management is possible or not, for now with a + * simple check looking for the specific "(0-1)" string. + */ + if (!strncmp (response, "(0-1)", 5)) { + *clcc_urcs_supported = TRUE; + return TRUE; + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "unexpected +CLCC test response: '%s'", response); + return FALSE; +} + +/*****************************************************************************/ + +GRegex * +mm_simtech_get_clcc_urc_regex (void) +{ + return g_regex_new ("\\r\\n(\\+CLCC: .*\\r\\n)+", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +gboolean +mm_simtech_parse_clcc_list (const gchar *str, + GList **out_list, + GError **error) +{ + /* Parse the URC contents as a plain +CLCC response, but make sure to skip first + * EOL in the string because the plain +CLCC response would never have that. + */ + return mm_3gpp_parse_clcc_response (mm_strip_tag (str, "\r\n"), out_list, error); +} + +void +mm_simtech_call_info_list_free (GList *call_info_list) +{ + mm_3gpp_call_info_list_free (call_info_list); +} diff --git a/plugins/simtech/mm-modem-helpers-simtech.h b/plugins/simtech/mm-modem-helpers-simtech.h new file mode 100644 index 00000000..66e5c467 --- /dev/null +++ b/plugins/simtech/mm-modem-helpers-simtech.h @@ -0,0 +1,39 @@ +/* -*- 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> + */ + +#ifndef MM_MODEM_HELPERS_SIMTECH_H +#define MM_MODEM_HELPERS_SIMTECH_H + +#include <glib.h> + +#include <ModemManager.h> +#include <mm-base-bearer.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +/*****************************************************************************/ +/* +CLCC URC helpers */ + +gboolean mm_simtech_parse_clcc_test (const gchar *response, + gboolean *clcc_urcs_supported, + GError **error); + +GRegex *mm_simtech_get_clcc_urc_regex (void); +gboolean mm_simtech_parse_clcc_list (const gchar *str, + GList **out_list, + GError **error); +void mm_simtech_call_info_list_free (GList *call_info_list); + +#endif /* MM_MODEM_HELPERS_SIMTECH_H */ diff --git a/plugins/simtech/mm-shared-simtech.c b/plugins/simtech/mm-shared-simtech.c index 756713c5..11be1a94 100644 --- a/plugins/simtech/mm-shared-simtech.c +++ b/plugins/simtech/mm-shared-simtech.c @@ -23,10 +23,12 @@ #include "mm-log.h" #include "mm-iface-modem.h" +#include "mm-iface-modem-voice.h" #include "mm-iface-modem-location.h" #include "mm-base-modem.h" #include "mm-base-modem-at.h" #include "mm-shared-simtech.h" +#include "mm-modem-helpers-simtech.h" /*****************************************************************************/ /* Private data context */ @@ -46,8 +48,19 @@ typedef struct { MMModemLocationSource supported_sources; MMModemLocationSource enabled_sources; FeatureSupport cgps_support; + /* voice */ + MMIfaceModemVoice *iface_modem_voice_parent; + FeatureSupport clcc_urc_support; + GRegex *clcc_urc_regex; } Private; +static void +private_free (Private *ctx) +{ + g_regex_unref (ctx->clcc_urc_regex); + g_slice_free (Private, ctx); +} + static Private * get_private (MMSharedSimtech *self) { @@ -58,16 +71,22 @@ get_private (MMSharedSimtech *self) priv = g_object_get_qdata (G_OBJECT (self), private_quark); if (!priv) { - priv = g_new0 (Private, 1); + priv = g_slice_new0 (Private); priv->supported_sources = MM_MODEM_LOCATION_SOURCE_NONE; priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE; priv->cgps_support = FEATURE_SUPPORT_UNKNOWN; + priv->clcc_urc_support = FEATURE_SUPPORT_UNKNOWN; + priv->clcc_urc_regex = mm_simtech_get_clcc_urc_regex (); + + /* Setup parent class' MMIfaceModemLocation and MMIfaceModemVoice */ - /* Setup parent class' MMIfaceModemLocation */ g_assert (MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_location_interface); priv->iface_modem_location_parent = MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_location_interface (self); - g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, g_free); + g_assert (MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_voice_interface); + priv->iface_modem_voice_parent = MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_voice_interface (self); + + g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); } return priv; @@ -489,6 +508,523 @@ mm_shared_simtech_enable_location_gathering (MMIfaceModemLocation *self, } /*****************************************************************************/ +/* Common enable/disable voice unsolicited events */ + +typedef struct { + gboolean enable; + MMPortSerialAt *primary; + MMPortSerialAt *secondary; + gchar *clcc_command; + gboolean clcc_primary_done; + gboolean clcc_secondary_done; +} VoiceUnsolicitedEventsContext; + +static void +voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx) +{ + g_clear_object (&ctx->secondary); + g_clear_object (&ctx->primary); + g_free (ctx->clcc_command); + g_slice_free (VoiceUnsolicitedEventsContext, ctx); +} + +static gboolean +common_voice_enable_disable_unsolicited_events_finish (MMSharedSimtech *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void run_voice_enable_disable_unsolicited_events (GTask *task); + +static void +clcc_command_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + VoiceUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_dbg ("Couldn't %s +CLCC reporting: '%s'", + ctx->enable ? "enable" : "disable", + error->message); + g_error_free (error); + } + + /* Continue on next port */ + run_voice_enable_disable_unsolicited_events (task); +} + +static void +run_voice_enable_disable_unsolicited_events (GTask *task) +{ + MMSharedSimtech *self; + Private *priv; + VoiceUnsolicitedEventsContext *ctx; + MMPortSerialAt *port = NULL; + + self = MM_SHARED_SIMTECH (g_task_get_source_object (task)); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + /* If +CLCC URCs not supported, we're done */ + if (priv->clcc_urc_support == FEATURE_NOT_SUPPORTED) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (!ctx->clcc_primary_done && ctx->primary) { + mm_dbg ("%s +CLCC extended list of current calls reporting in primary port...", + ctx->enable ? "Enabling" : "Disabling"); + ctx->clcc_primary_done = TRUE; + port = ctx->primary; + } else if (!ctx->clcc_secondary_done && ctx->secondary) { + mm_dbg ("%s +CLCC extended list of current calls reporting in secondary port...", + ctx->enable ? "Enabling" : "Disabling"); + ctx->clcc_secondary_done = TRUE; + port = ctx->secondary; + } + + if (port) { + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + port, + ctx->clcc_command, + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)clcc_command_ready, + task); + return; + } + + /* Fully done now */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +common_voice_enable_disable_unsolicited_events (MMSharedSimtech *self, + gboolean enable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + VoiceUnsolicitedEventsContext *ctx; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_slice_new0 (VoiceUnsolicitedEventsContext); + ctx->enable = enable; + if (enable) + ctx->clcc_command = g_strdup ("+CLCC=1"); + else + ctx->clcc_command = g_strdup ("+CLCC=0"); + ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); + ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); + g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free); + + run_voice_enable_disable_unsolicited_events (task); +} + +/*****************************************************************************/ +/* Disable unsolicited events (Voice interface) */ + +gboolean +mm_shared_simtech_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!priv->iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) { + mm_warn ("Couldn't disable parent voice unsolicited events: %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +voice_disable_unsolicited_events_ready (MMSharedSimtech *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + GError *error = NULL; + + if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) { + mm_warn ("Couldn't disable Simtech-specific voice unsolicited events: %s", error->message); + g_error_free (error); + } + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events_finish); + + /* Chain up parent's disable */ + priv->iface_modem_voice_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_VOICE (self), + (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready, + task); +} + +void +mm_shared_simtech_voice_disable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* our own disabling first */ + common_voice_enable_disable_unsolicited_events (MM_SHARED_SIMTECH (self), + FALSE, + (GAsyncReadyCallback) voice_disable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Enable unsolicited events (Voice interface) */ + +gboolean +mm_shared_simtech_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +voice_enable_unsolicited_events_ready (MMSharedSimtech *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) { + mm_warn ("Couldn't enable Simtech-specific voice unsolicited events: %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!priv->iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) { + mm_warn ("Couldn't enable parent voice unsolicited events: %s", error->message); + g_error_free (error); + } + + /* our own enabling next */ + common_voice_enable_disable_unsolicited_events (MM_SHARED_SIMTECH (self), + TRUE, + (GAsyncReadyCallback) voice_enable_unsolicited_events_ready, + task); +} + +void +mm_shared_simtech_voice_enable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events_finish); + + /* chain up parent's enable first */ + priv->iface_modem_voice_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Common setup/cleanup voice unsolicited events */ + +static void +clcc_urc_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMSharedSimtech *self) +{ + gchar *full; + GError *error = NULL; + GList *call_info_list = NULL; + + full = g_match_info_fetch (match_info, 0); + + if (!mm_simtech_parse_clcc_list (full, &call_info_list, &error)) { + mm_warn ("couldn't parse +CLCC list in URC: %s", error->message); + g_error_free (error); + } else + mm_iface_modem_voice_report_all_calls (MM_IFACE_MODEM_VOICE (self), call_info_list); + + mm_simtech_call_info_list_free (call_info_list); + g_free (full); +} + +static void +common_voice_setup_cleanup_unsolicited_events (MMSharedSimtech *self, + gboolean enable) +{ + Private *priv; + MMPortSerialAt *ports[2]; + guint i; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + /* If +CLCC URCs not supported, we're done */ + if (priv->clcc_urc_support == FEATURE_NOT_SUPPORTED) + return; + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->clcc_urc_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)clcc_urc_received : NULL, + enable ? self : NULL, + NULL); + } +} + +/*****************************************************************************/ +/* Cleanup unsolicited events (Voice interface) */ + +gboolean +mm_shared_simtech_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) { + mm_warn ("Couldn't cleanup parent voice unsolicited events: %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_simtech_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish); + + /* our own cleanup first */ + common_voice_setup_cleanup_unsolicited_events (MM_SHARED_SIMTECH (self), FALSE); + + /* Chain up parent's cleanup */ + priv->iface_modem_voice_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Setup unsolicited events (Voice interface) */ + +gboolean +mm_shared_simtech_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!priv->iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) { + mm_warn ("Couldn't setup parent voice unsolicited events: %s", error->message); + g_error_free (error); + } + + /* our own setup next */ + common_voice_setup_cleanup_unsolicited_events (MM_SHARED_SIMTECH (self), TRUE); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_simtech_voice_setup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events); + g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events_finish); + + /* chain up parent's setup first */ + priv->iface_modem_voice_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Check if Voice supported (Voice interface) */ + +gboolean +mm_shared_simtech_voice_check_support_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +clcc_format_check_ready (MMBroadbandModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + GError *error = NULL; + const gchar *response; + gboolean clcc_urc_supported = FALSE; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL); + if (response && !mm_simtech_parse_clcc_test (response, &clcc_urc_supported, &error)) { + mm_dbg ("failed checking CLCC URC support: %s", error->message); + g_clear_error (&error); + } + + priv->clcc_urc_support = (clcc_urc_supported ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED); + mm_dbg ("modem %s +CLCC URCs", (priv->clcc_urc_support == FEATURE_SUPPORTED) ? "supports" : "doesn't support"); + + /* If +CLCC URC supported we won't need polling in the parent */ + g_object_set (self, + MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, (priv->clcc_urc_support == FEATURE_SUPPORTED), + NULL); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_voice_check_support_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + GError *error = NULL; + + priv = get_private (MM_SHARED_SIMTECH (self)); + if (!priv->iface_modem_voice_parent->check_support_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* voice is supported, check if +CLCC URCs are available */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CLCC=?", + 3, + TRUE, + (GAsyncReadyCallback) clcc_format_check_ready, + task); +} + +void +mm_shared_simtech_voice_check_support (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_voice_parent); + g_assert (priv->iface_modem_voice_parent->check_support); + g_assert (priv->iface_modem_voice_parent->check_support_finish); + + /* chain up parent's setup first */ + priv->iface_modem_voice_parent->check_support ( + self, + (GAsyncReadyCallback)parent_voice_check_support_ready, + task); +} + +/*****************************************************************************/ static void shared_simtech_init (gpointer g_iface) diff --git a/plugins/simtech/mm-shared-simtech.h b/plugins/simtech/mm-shared-simtech.h index 48342f3c..3382869a 100644 --- a/plugins/simtech/mm-shared-simtech.h +++ b/plugins/simtech/mm-shared-simtech.h @@ -26,7 +26,6 @@ #include "mm-iface-modem.h" #include "mm-iface-modem-location.h" #include "mm-iface-modem-voice.h" -#include "mm-iface-modem-time.h" #define MM_TYPE_SHARED_SIMTECH (mm_shared_simtech_get_type ()) #define MM_SHARED_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_SIMTECH, MMSharedSimtech)) @@ -40,6 +39,9 @@ struct _MMSharedSimtech { /* Peek location interface of the parent class of the object */ MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedSimtech *self); + + /* Peek voice interface of the parent class of the object */ + MMIfaceModemVoice * (* peek_parent_voice_interface) (MMSharedSimtech *self); }; GType mm_shared_simtech_get_type (void); @@ -70,4 +72,43 @@ gboolean mm_shared_simtech_disable_location_gathering_finish (MMIfa GAsyncResult *res, GError **error); + +/*****************************************************************************/ +/* Voice interface */ + +void mm_shared_simtech_voice_check_support (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_check_support_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_voice_setup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_voice_enable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_voice_disable_unsolicited_events (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + #endif /* MM_SHARED_SIMTECH_H */ diff --git a/plugins/simtech/tests/test-modem-helpers-simtech.c b/plugins/simtech/tests/test-modem-helpers-simtech.c new file mode 100644 index 00000000..a93d903a --- /dev/null +++ b/plugins/simtech/tests/test-modem-helpers-simtech.c @@ -0,0 +1,177 @@ +/* -*- 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 <glib.h> +#include <glib-object.h> +#include <locale.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-simtech.h" + +/*****************************************************************************/ +/* Test +CLCC URCs */ + +static void +common_test_clcc_urc (const gchar *urc, + const MMCallInfo *expected_call_info_list, + guint expected_call_info_list_size) +{ + GError *error = NULL; + GRegex *clcc_regex = NULL; + gboolean result; + GMatchInfo *match_info = NULL; + gchar *str; + GList *call_info_list = NULL; + GList *l; + + clcc_regex = mm_simtech_get_clcc_urc_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (clcc_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + /* read full matched content */ + str = g_match_info_fetch (match_info, 0); + g_assert (str); + + result = mm_simtech_parse_clcc_list (str, &call_info_list, &error); + g_assert_no_error (error); + g_assert (result); + + g_debug ("found %u calls", g_list_length (call_info_list)); + + if (expected_call_info_list) { + g_assert (call_info_list); + g_assert_cmpuint (g_list_length (call_info_list), ==, expected_call_info_list_size); + } else + g_assert (!call_info_list); + + for (l = call_info_list; l; l = g_list_next (l)) { + const MMCallInfo *call_info = (const MMCallInfo *)(l->data); + gboolean found = FALSE; + guint i; + + g_debug ("call at index %u: direction %s, state %s, number %s", + call_info->index, + mm_call_direction_get_string (call_info->direction), + mm_call_state_get_string (call_info->state), + call_info->number ? call_info->number : "n/a"); + + for (i = 0; !found && i < expected_call_info_list_size; i++) + found = ((call_info->index == expected_call_info_list[i].index) && + (call_info->direction == expected_call_info_list[i].direction) && + (call_info->state == expected_call_info_list[i].state) && + (g_strcmp0 (call_info->number, expected_call_info_list[i].number) == 0)); + + g_assert (found); + } + + g_match_info_free (match_info); + g_regex_unref (clcc_regex); + g_free (str); + + mm_simtech_call_info_list_free (call_info_list); +} + +static void +test_clcc_urc_single (void) +{ + static const MMCallInfo expected_call_info_list[] = { + { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, "123456789" } + }; + + const gchar *urc = + "\r\n+CLCC: 1,1,0,0,0,\"123456789\",161" + "\r\n"; + + common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list)); +} + +static void +test_clcc_urc_multiple (void) +{ + static const MMCallInfo expected_call_info_list[] = { + { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, NULL }, + { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, "123456789" }, + { 3, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, "987654321" }, + }; + + const gchar *urc = + "\r\n+CLCC: 1,1,0,0,0" /* number unknown */ + "\r\n+CLCC: 2,1,0,0,0,\"123456789\",161" + "\r\n+CLCC: 3,1,0,0,0,\"987654321\",161,\"Alice\"" + "\r\n"; + + common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list)); +} + +static void +test_clcc_urc_complex (void) +{ + static const MMCallInfo expected_call_info_list[] = { + { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, "123456789" }, + { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_WAITING, "987654321" }, + }; + + const gchar *urc = + "\r\n^CIEV: 1,0" /* some different URC before our match */ + "\r\n+CLCC: 1,1,0,0,0,\"123456789\",161" + "\r\n+CLCC: 2,1,5,0,0,\"987654321\",161" + "\r\n^CIEV: 1,0" /* some different URC after our match */ + "\r\n"; + + common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list)); +} + +/*****************************************************************************/ + +void +_mm_log (const char *loc, + const char *func, + guint32 level, + const char *fmt, + ...) +{ + va_list args; + gchar *msg; + + if (!g_test_verbose ()) + return; + + va_start (args, fmt); + msg = g_strdup_vprintf (fmt, args); + va_end (args); + g_print ("%s\n", msg); + g_free (msg); +} + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/simtech/clcc/urc/single", test_clcc_urc_single); + g_test_add_func ("/MM/simtech/clcc/urc/multiple", test_clcc_urc_multiple); + g_test_add_func ("/MM/simtech/clcc/urc/complex", test_clcc_urc_complex); + + return g_test_run (); +} |