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/simtech | |
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/simtech')
-rw-r--r-- | src/plugins/simtech/77-mm-simtech-port-types.rules | 59 | ||||
-rw-r--r-- | src/plugins/simtech/mm-broadband-modem-qmi-simtech.c | 127 | ||||
-rw-r--r-- | src/plugins/simtech/mm-broadband-modem-qmi-simtech.h | 47 | ||||
-rw-r--r-- | src/plugins/simtech/mm-broadband-modem-simtech.c | 1351 | ||||
-rw-r--r-- | src/plugins/simtech/mm-broadband-modem-simtech.h | 51 | ||||
-rw-r--r-- | src/plugins/simtech/mm-modem-helpers-simtech.c | 209 | ||||
-rw-r--r-- | src/plugins/simtech/mm-modem-helpers-simtech.h | 67 | ||||
-rw-r--r-- | src/plugins/simtech/mm-plugin-simtech.c | 98 | ||||
-rw-r--r-- | src/plugins/simtech/mm-plugin-simtech.h | 42 | ||||
-rw-r--r-- | src/plugins/simtech/mm-shared-simtech.c | 1261 | ||||
-rw-r--r-- | src/plugins/simtech/mm-shared-simtech.h | 129 | ||||
-rw-r--r-- | src/plugins/simtech/tests/test-modem-helpers-simtech.c | 324 |
12 files changed, 3765 insertions, 0 deletions
diff --git a/src/plugins/simtech/77-mm-simtech-port-types.rules b/src/plugins/simtech/77-mm-simtech-port-types.rules new file mode 100644 index 00000000..e51a60dd --- /dev/null +++ b/src/plugins/simtech/77-mm-simtech-port-types.rules @@ -0,0 +1,59 @@ +# do not edit this file, it will be overwritten on update + +# Simtech makes modules that other companies rebrand, like: +# +# A-LINK 3GU +# SCT UM300 +# +# Most of these values were scraped from various SimTech-based Windows +# driver .inf files. *mdm.inf lists the main command ports, while +# *ser.inf lists the aux ports that may be used for PPP. + + +ACTION!="add|change|move|bind", GOTO="mm_simtech_port_types_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1e0e", GOTO="mm_simtech_port_types" +GOTO="mm_simtech_port_types_end" + +LABEL="mm_simtech_port_types" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# A-LINK 3GU +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Prolink PH-300 +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9100", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9100", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# SCT UM300 +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9200", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9200", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# SIM7000, SIM7100, SIM7600... +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AUDIO}="1" + +# SIM7070, SIM7080, SIM7090 (default layout)... +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1" +# Disable CPOL based features +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1" + + +# SIM7070, SIM7080, SIM7090 (secondary layout)... +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1" +# Disable CPOL based features +ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1" + +LABEL="mm_simtech_port_types_end" diff --git a/src/plugins/simtech/mm-broadband-modem-qmi-simtech.c b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.c new file mode 100644 index 00000000..93ff8576 --- /dev/null +++ b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.c @@ -0,0 +1,127 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.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)) + +/*****************************************************************************/ + +MMBroadbandModemQmiSimtech * +mm_broadband_modem_qmi_simtech_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, + 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, + /* QMI modem supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_BROADBAND_MODEM_INDICATORS_DISABLED, TRUE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_qmi_simtech_init (MMBroadbandModemQmiSimtech *self) +{ +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_simtech_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_simtech_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_simtech_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_simtech_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_simtech_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_simtech_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedSimtech *self) +{ + return iface_modem_location_parent; +} + +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; + iface->setup_in_call_audio_channel = mm_shared_simtech_voice_setup_in_call_audio_channel; + iface->setup_in_call_audio_channel_finish = mm_shared_simtech_voice_setup_in_call_audio_channel_finish; + iface->cleanup_in_call_audio_channel = mm_shared_simtech_voice_cleanup_in_call_audio_channel; + iface->cleanup_in_call_audio_channel_finish = mm_shared_simtech_voice_cleanup_in_call_audio_channel_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 +mm_broadband_modem_qmi_simtech_class_init (MMBroadbandModemQmiSimtechClass *klass) +{ +} diff --git a/src/plugins/simtech/mm-broadband-modem-qmi-simtech.h b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.h new file mode 100644 index 00000000..2f5b819b --- /dev/null +++ b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.h @@ -0,0 +1,47 @@ +/* -*- 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_BROADBAND_MODEM_QMI_SIMTECH_QMI_H +#define MM_BROADBAND_MODEM_QMI_SIMTECH_QMI_H + +#include "mm-broadband-modem-qmi.h" + +#define MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH (mm_broadband_modem_qmi_simtech_get_type ()) +#define MM_BROADBAND_MODEM_QMI_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtech)) +#define MM_BROADBAND_MODEM_QMI_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtechClass)) +#define MM_IS_BROADBAND_MODEM_QMI_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH)) +#define MM_IS_BROADBAND_MODEM_QMI_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH)) +#define MM_BROADBAND_MODEM_QMI_SIMTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtechClass)) + +typedef struct _MMBroadbandModemQmiSimtech MMBroadbandModemQmiSimtech; +typedef struct _MMBroadbandModemQmiSimtechClass MMBroadbandModemQmiSimtechClass; + +struct _MMBroadbandModemQmiSimtech { + MMBroadbandModemQmi parent; +}; + +struct _MMBroadbandModemQmiSimtechClass{ + MMBroadbandModemQmiClass parent; +}; + +GType mm_broadband_modem_qmi_simtech_get_type (void); + +MMBroadbandModemQmiSimtech *mm_broadband_modem_qmi_simtech_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_QMI_SIMTECH_H */ diff --git a/src/plugins/simtech/mm-broadband-modem-simtech.c b/src/plugins/simtech/mm-broadband-modem-simtech.c new file mode 100644 index 00000000..2ca0c6ae --- /dev/null +++ b/src/plugins/simtech/mm-broadband-modem-simtech.c @@ -0,0 +1,1351 @@ +/* -*- 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> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "ModemManager.h" +#include "mm-modem-helpers.h" +#include "mm-log-object.h" +#include "mm-base-modem-at.h" +#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 { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED +} FeatureSupport; + +struct _MMBroadbandModemSimtechPrivate { + FeatureSupport cnsmod_support; + FeatureSupport autocsq_support; + GRegex *cnsmod_regex; + GRegex *csq_regex; +}; + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static MMModemAccessTechnology +simtech_act_to_mm_act (guint nsmod) +{ + static const MMModemAccessTechnology simtech_act_to_mm_act_map[] = { + [0] = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN, + [1] = MM_MODEM_ACCESS_TECHNOLOGY_GSM, + [2] = MM_MODEM_ACCESS_TECHNOLOGY_GPRS, + [3] = MM_MODEM_ACCESS_TECHNOLOGY_EDGE, + [4] = MM_MODEM_ACCESS_TECHNOLOGY_UMTS, + [5] = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA, + [6] = MM_MODEM_ACCESS_TECHNOLOGY_HSUPA, + [7] = MM_MODEM_ACCESS_TECHNOLOGY_HSPA, + [8] = MM_MODEM_ACCESS_TECHNOLOGY_LTE, + }; + + return (nsmod < G_N_ELEMENTS (simtech_act_to_mm_act_map) ? simtech_act_to_mm_act_map[nsmod] : MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); +} + +static void +simtech_tech_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemSimtech *self) +{ + guint simtech_act = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &simtech_act)) + return; + + mm_iface_modem_update_access_technologies ( + MM_IFACE_MODEM (self), + simtech_act_to_mm_act (simtech_act), + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); +} + +static void +simtech_signal_changed (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemSimtech *self) +{ + guint quality = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &quality)) + return; + + if (quality != 99) + quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31; + else + quality = 0; + + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); +} + +static void +set_unsolicited_events_handlers (MMBroadbandModemSimtech *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->cnsmod_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)simtech_tech_changed : NULL, + enable ? self : NULL, + NULL); + + /* Signal quality related */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->csq_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)simtech_signal_changed : NULL, + enable ? self : NULL, + NULL); + } +} + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_SIMTECH (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_SIMTECH (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)); +} + +/*****************************************************************************/ +/* Enable unsolicited events (3GPP interface) */ + +typedef enum { + ENABLE_UNSOLICITED_EVENTS_STEP_FIRST, + ENABLE_UNSOLICITED_EVENTS_STEP_PARENT, + ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_CNSMOD, + ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_CNSMOD, + ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_AUTOCSQ, + ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_AUTOCSQ, + ENABLE_UNSOLICITED_EVENTS_STEP_LAST, +} EnableUnsolicitedEventsStep; + +typedef struct { + EnableUnsolicitedEventsStep step; +} EnableUnsolicitedEventsContext; + +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 enable_unsolicited_events_context_step (GTask *task); + +static void +autocsq_set_enabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + EnableUnsolicitedEventsContext *ctx; + GError *error = NULL; + gboolean csq_urcs_enabled = FALSE; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't enable automatic signal quality reporting: %s", error->message); + g_error_free (error); + } else + csq_urcs_enabled = TRUE; + + /* Disable access technology polling if we can use the +CSQ URCs */ + g_object_set (self, + MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, csq_urcs_enabled, + NULL); + + /* go to next step */ + ctx->step++; + enable_unsolicited_events_context_step (task); +} + +static void +autocsq_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemSimtech *self; + EnableUnsolicitedEventsContext *ctx; + + self = MM_BROADBAND_MODEM_SIMTECH (_self); + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (_self, res, NULL)) + self->priv->autocsq_support = FEATURE_NOT_SUPPORTED; + else + self->priv->autocsq_support = FEATURE_SUPPORTED; + + /* go to next step */ + ctx->step++; + enable_unsolicited_events_context_step (task); +} + +static void +cnsmod_set_enabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + EnableUnsolicitedEventsContext *ctx; + GError *error = NULL; + gboolean cnsmod_urcs_enabled = FALSE; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't enable automatic access technology reporting: %s", error->message); + g_error_free (error); + } else + cnsmod_urcs_enabled = TRUE; + + /* Disable access technology polling if we can use the +CNSMOD URCs */ + g_object_set (self, + MM_IFACE_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED, cnsmod_urcs_enabled, + NULL); + + /* go to next step */ + ctx->step++; + enable_unsolicited_events_context_step (task); +} + +static void +cnsmod_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemSimtech *self; + EnableUnsolicitedEventsContext *ctx; + + self = MM_BROADBAND_MODEM_SIMTECH (_self); + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (_self, res, NULL)) + self->priv->cnsmod_support = FEATURE_NOT_SUPPORTED; + else + self->priv->cnsmod_support = FEATURE_SUPPORTED; + + /* go to next step */ + ctx->step++; + enable_unsolicited_events_context_step (task); +} + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + EnableUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* go to next step */ + ctx->step++; + enable_unsolicited_events_context_step (task); +} + +static void +enable_unsolicited_events_context_step (GTask *task) +{ + MMBroadbandModemSimtech *self; + EnableUnsolicitedEventsContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case ENABLE_UNSOLICITED_EVENTS_STEP_FIRST: + ctx->step++; + /* fall through */ + + case ENABLE_UNSOLICITED_EVENTS_STEP_PARENT: + iface_modem_3gpp_parent->enable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + task); + return; + + case ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_CNSMOD: + if (self->priv->cnsmod_support == FEATURE_SUPPORT_UNKNOWN) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CNSMOD=?", + 3, + TRUE, + (GAsyncReadyCallback)cnsmod_test_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_CNSMOD: + if (self->priv->cnsmod_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + /* Autoreport +CNSMOD when it changes */ + "+CNSMOD=1", + 20, + FALSE, + (GAsyncReadyCallback)cnsmod_set_enabled_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_AUTOCSQ: + if (self->priv->autocsq_support == FEATURE_SUPPORT_UNKNOWN) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+AUTOCSQ=?", + 3, + TRUE, + (GAsyncReadyCallback)autocsq_test_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_AUTOCSQ: + if (self->priv->autocsq_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + /* Autoreport+ CSQ (first arg), and only report when it changes (second arg) */ + "+AUTOCSQ=1,1", + 20, + FALSE, + (GAsyncReadyCallback)autocsq_set_enabled_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLE_UNSOLICITED_EVENTS_STEP_LAST: + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnableUnsolicitedEventsContext *ctx; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_new (EnableUnsolicitedEventsContext, 1); + ctx->step = ENABLE_UNSOLICITED_EVENTS_STEP_FIRST; + g_task_set_task_data (task, ctx, g_free); + + enable_unsolicited_events_context_step (task); +} + +/*****************************************************************************/ +/* Disable unsolicited events (3GPP interface) */ + +typedef enum { + DISABLE_UNSOLICITED_EVENTS_STEP_FIRST, + DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_AUTOCSQ, + DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_CNSMOD, + DISABLE_UNSOLICITED_EVENTS_STEP_PARENT, + DISABLE_UNSOLICITED_EVENTS_STEP_LAST, +} DisableUnsolicitedEventsStep; + +typedef struct { + DisableUnsolicitedEventsStep step; +} DisableUnsolicitedEventsContext; + +static gboolean +modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void disable_unsolicited_events_context_step (GTask *task); + +static void +parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + DisableUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* go to next step */ + ctx->step++; + disable_unsolicited_events_context_step (task); +} + +static void +cnsmod_set_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + DisableUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't disable automatic access technology reporting: %s", error->message); + g_error_free (error); + } + + /* go to next step */ + ctx->step++; + disable_unsolicited_events_context_step (task); +} + +static void +autocsq_set_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + DisableUnsolicitedEventsContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't disable automatic signal quality reporting: %s", error->message); + g_error_free (error); + } + + /* go to next step */ + ctx->step++; + disable_unsolicited_events_context_step (task); +} + +static void +disable_unsolicited_events_context_step (GTask *task) +{ + MMBroadbandModemSimtech *self; + DisableUnsolicitedEventsContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case DISABLE_UNSOLICITED_EVENTS_STEP_FIRST: + ctx->step++; + /* fall through */ + + case DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_AUTOCSQ: + if (self->priv->autocsq_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+AUTOCSQ=0", + 20, + FALSE, + (GAsyncReadyCallback)autocsq_set_disabled_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_CNSMOD: + if (self->priv->cnsmod_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CNSMOD=0", + 20, + FALSE, + (GAsyncReadyCallback)cnsmod_set_disabled_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case DISABLE_UNSOLICITED_EVENTS_STEP_PARENT: + iface_modem_3gpp_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, + task); + return; + + case DISABLE_UNSOLICITED_EVENTS_STEP_LAST: + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DisableUnsolicitedEventsContext *ctx; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_new (DisableUnsolicitedEventsContext, 1); + ctx->step = DISABLE_UNSOLICITED_EVENTS_STEP_FIRST; + g_task_set_task_data (task, ctx, g_free); + + disable_unsolicited_events_context_step (task); +} + +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + GError *inner_error = NULL; + gssize act; + + act = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + *access_technologies = (MMModemAccessTechnology) act; + *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; + return TRUE; +} + +static void +cnsmod_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response, *p; + GError *error = NULL; + + 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; + } + + p = mm_strip_tag (response, "+CNSMOD:"); + if (p) + p = strchr (p, ','); + + if (!p || !isdigit (*(p + 1))) + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse the +CNSMOD response: '%s'", + response); + else + g_task_return_int (task, simtech_act_to_mm_act (atoi (p + 1))); + g_object_unref (task); +} + +static void +load_access_technologies (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemSimtech *self; + GTask *task; + + self = MM_BROADBAND_MODEM_SIMTECH (_self); + task = g_task_new (self, NULL, callback, user_data); + + /* Launch query only for 3GPP modems */ + if (!mm_iface_modem_is_3gpp (_self)) { + g_task_return_int (task, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); + g_object_unref (task); + return; + } + + g_assert (self->priv->cnsmod_support != FEATURE_SUPPORT_UNKNOWN); + if (self->priv->cnsmod_support == FEATURE_NOT_SUPPORTED) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Loading access technologies with +CNSMOD is not supported"); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "AT+CNSMOD?", + 3, + FALSE, + (GAsyncReadyCallback)cnsmod_query_ready, + task); +} + +/*****************************************************************************/ +/* Load signal quality (Modem interface) */ + +static guint +load_signal_quality_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + gssize value; + + value = g_task_propagate_int (G_TASK (res), error); + return value < 0 ? 0 : value; +} + +static void +csq_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response, *p; + GError *error = NULL; + gint quality; + gint ber; + + 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; + } + + /* Given that we may have enabled AUTOCSQ support, it is totally possible + * to get an empty string at this point, because the +CSQ reply may have + * been processed as an URC already. If we ever see this, we should not return + * an error, because that would reset the reported signal quality to 0 :/ + * So, in this case, return the last cached signal quality value. */ + if (!response[0]) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS, + "already refreshed via URCs"); + g_object_unref (task); + return; + } + + p = mm_strip_tag (response, "+CSQ:"); + if (sscanf (p, "%d, %d", &quality, &ber)) { + if (quality != 99) + quality = CLAMP (quality, 0, 31) * 100 / 31; + else + quality = 0; + g_task_return_int (task, quality); + g_object_unref (task); + return; + } + + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Could not parse signal quality results"); + g_object_unref (task); +} + +static void +load_signal_quality (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CSQ", + 3, + FALSE, + (GAsyncReadyCallback)csq_query_ready, + task); +} + +/*****************************************************************************/ +/* 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) */ + +typedef struct { + MMModemMode allowed; + MMModemMode preferred; +} LoadCurrentModesResult; + +typedef struct { + gint acqord; + gint modepref; +} LoadCurrentModesContext; + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + LoadCurrentModesResult *result; + + result = g_task_propagate_pointer (G_TASK (res), error); + if (!result) + return FALSE; + + *allowed = result->allowed; + *preferred = result->preferred; + g_free (result); + return TRUE; +} + +static void +cnmp_query_ready (MMBroadbandModemSimtech *self, + GAsyncResult *res, + GTask *task) +{ + LoadCurrentModesContext *ctx; + LoadCurrentModesResult *result; + const gchar *response, *p; + GError *error = NULL; + + 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; + } + + ctx = g_task_get_task_data (task); + + p = mm_strip_tag (response, "+CNMP:"); + if (!p) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse the mode preference response: '%s'", + response); + g_object_unref (task); + return; + } + + result = g_new (LoadCurrentModesResult, 1); + result->allowed = MM_MODEM_MODE_NONE; + result->preferred = MM_MODEM_MODE_NONE; + + ctx->modepref = atoi (p); + switch (ctx->modepref) { + case 2: + /* Automatic */ + switch (ctx->acqord) { + case 0: + result->allowed = MM_MODEM_MODE_ANY; + result->preferred = MM_MODEM_MODE_NONE; + break; + case 1: + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + result->preferred = MM_MODEM_MODE_2G; + break; + case 2: + result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + result->preferred = MM_MODEM_MODE_3G; + break; + default: + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unknown acquisition order preference: '%d'", + ctx->acqord); + g_object_unref (task); + g_free (result); + return; + } + break; + + case 13: + /* GSM only */ + result->allowed = MM_MODEM_MODE_2G; + result->preferred = MM_MODEM_MODE_NONE; + break; + + case 14: + /* WCDMA only */ + result->allowed = MM_MODEM_MODE_3G; + result->preferred = MM_MODEM_MODE_NONE; + break; + + default: + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unknown mode preference: '%d'", + ctx->modepref); + g_object_unref (task); + g_free (result); + return; + } + + g_task_return_pointer (task, result, g_free); + g_object_unref (task); +} + +static void +cnaop_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LoadCurrentModesContext *ctx; + const gchar *response, *p; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + p = mm_strip_tag (response, "+CNAOP:"); + if (p) + ctx->acqord = atoi (p); + + if (ctx->acqord < 0 || ctx->acqord > 2) { + g_task_return_new_error ( + task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse the acquisition order response: '%s'", + response); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CNMP?", + 3, + FALSE, + (GAsyncReadyCallback)cnmp_query_ready, + task); +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LoadCurrentModesContext *ctx; + + ctx = g_new (LoadCurrentModesContext, 1); + ctx->acqord = -1; + ctx->modepref = -1; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CNAOP?", + 3, + FALSE, + (GAsyncReadyCallback)cnaop_query_ready, + task); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +typedef struct { + guint nmp; /* mode preference */ + guint naop; /* acquisition order */ +} SetCurrentModesContext; + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cnaop_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +cnmp_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + SetCurrentModesContext *ctx; + GError *error = NULL; + gchar *command; + + ctx = g_task_get_task_data (task); + + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) { + /* Let the error be critical. */ + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + command = g_strdup_printf ("+CNAOP=%u", ctx->naop); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)cnaop_set_ready, + task); + g_free (command); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + SetCurrentModesContext *ctx; + gchar *command; + + /* Defaults: automatic search */ + ctx = g_new (SetCurrentModesContext, 1); + ctx->nmp = 2; + ctx->naop = 0; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE) { + /* defaults nmp and naop */ + } else if (allowed == MM_MODEM_MODE_2G) { + ctx->nmp = 13; + ctx->naop = 0; + } else if (allowed == MM_MODEM_MODE_3G) { + ctx->nmp = 14; + ctx->naop = 0; + } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { + /* default nmp */ + if (preferred == MM_MODEM_MODE_2G) + ctx->naop = 3; + else if (preferred == MM_MODEM_MODE_3G) + ctx->naop = 2; + else + /* default naop */ + ctx->naop = 0; + } else { + 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 ("+CNMP=%u", ctx->nmp); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 3, + FALSE, + (GAsyncReadyCallback)cnmp_set_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* 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_simtech_parent_class)->setup_ports (self); + + /* Now reset the unsolicited messages we'll handle when enabled */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_SIMTECH (self), FALSE); +} + +/*****************************************************************************/ + +MMBroadbandModemSimtech * +mm_broadband_modem_simtech_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_SIMTECH, + 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_BROADBAND_MODEM_INDICATORS_DISABLED, TRUE, + NULL); +} + +static void +mm_broadband_modem_simtech_init (MMBroadbandModemSimtech *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_SIMTECH, + MMBroadbandModemSimtechPrivate); + + self->priv->cnsmod_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->autocsq_support = FEATURE_SUPPORT_UNKNOWN; + + self->priv->cnsmod_regex = g_regex_new ("\\r\\n\\+CNSMOD:\\s*(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->csq_regex = g_regex_new ("\\r\\n\\+CSQ:\\s*(\\d+),(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemSimtech *self = MM_BROADBAND_MODEM_SIMTECH (object); + + g_regex_unref (self->priv->cnsmod_regex); + g_regex_unref (self->priv->csq_regex); + + G_OBJECT_CLASS (mm_broadband_modem_simtech_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->load_signal_quality = load_signal_quality; + iface->load_signal_quality_finish = load_signal_quality_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->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 +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_simtech_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_simtech_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_simtech_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_simtech_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_simtech_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_simtech_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedSimtech *self) +{ + return iface_modem_location_parent; +} + +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; + iface->setup_in_call_audio_channel = mm_shared_simtech_voice_setup_in_call_audio_channel; + iface->setup_in_call_audio_channel_finish = mm_shared_simtech_voice_setup_in_call_audio_channel_finish; + iface->cleanup_in_call_audio_channel = mm_shared_simtech_voice_cleanup_in_call_audio_channel; + iface->cleanup_in_call_audio_channel_finish = mm_shared_simtech_voice_cleanup_in_call_audio_channel_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 +mm_broadband_modem_simtech_class_init (MMBroadbandModemSimtechClass *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 (MMBroadbandModemSimtechPrivate)); + + object_class->finalize = finalize; + + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/simtech/mm-broadband-modem-simtech.h b/src/plugins/simtech/mm-broadband-modem-simtech.h new file mode 100644 index 00000000..a2b57fea --- /dev/null +++ b/src/plugins/simtech/mm-broadband-modem-simtech.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_SIMTECH_H +#define MM_BROADBAND_MODEM_SIMTECH_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_SIMTECH (mm_broadband_modem_simtech_get_type ()) +#define MM_BROADBAND_MODEM_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_SIMTECH, MMBroadbandModemSimtech)) +#define MM_BROADBAND_MODEM_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_SIMTECH, MMBroadbandModemSimtechClass)) +#define MM_IS_BROADBAND_MODEM_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_SIMTECH)) +#define MM_IS_BROADBAND_MODEM_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_SIMTECH)) +#define MM_BROADBAND_MODEM_SIMTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_SIMTECH, MMBroadbandModemSimtechClass)) + +typedef struct _MMBroadbandModemSimtech MMBroadbandModemSimtech; +typedef struct _MMBroadbandModemSimtechClass MMBroadbandModemSimtechClass; +typedef struct _MMBroadbandModemSimtechPrivate MMBroadbandModemSimtechPrivate; + +struct _MMBroadbandModemSimtech { + MMBroadbandModem parent; + MMBroadbandModemSimtechPrivate *priv; +}; + +struct _MMBroadbandModemSimtechClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_simtech_get_type (void); + +MMBroadbandModemSimtech *mm_broadband_modem_simtech_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_SIMTECH_H */ diff --git a/src/plugins/simtech/mm-modem-helpers-simtech.c b/src/plugins/simtech/mm-modem-helpers-simtech.c new file mode 100644 index 00000000..0403c145 --- /dev/null +++ b/src/plugins/simtech/mm-modem-helpers-simtech.c @@ -0,0 +1,209 @@ +/* -*- 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-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, + gpointer log_object, + 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"), log_object, out_list, error); +} + +void +mm_simtech_call_info_list_free (GList *call_info_list) +{ + mm_3gpp_call_info_list_free (call_info_list); +} + +/*****************************************************************************/ + +/* + * <CR><LF>VOICE CALL: BEGIN<CR><LF> + * <CR><LF>VOICE CALL: END: 000041<CR><LF> + */ +GRegex * +mm_simtech_get_voice_call_urc_regex (void) +{ + return g_regex_new ("\\r\\nVOICE CALL:\\s*([A-Z]+)(?::\\s*(\\d+))?\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +gboolean +mm_simtech_parse_voice_call_urc (GMatchInfo *match_info, + gboolean *start_or_stop, + guint *duration, + GError **error) +{ + GError *inner_error = NULL; + gchar *str; + + str = mm_get_string_unquoted_from_match_info (match_info, 1); + if (!str) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't read voice call URC action"); + goto out; + } + + if (g_strcmp0 (str, "BEGIN") == 0) { + *start_or_stop = TRUE; + *duration = 0; + goto out; + } + + if (g_strcmp0 (str, "END") == 0) { + *start_or_stop = FALSE; + if (!mm_get_uint_from_match_info (match_info, 2, duration)) + *duration = 0; + goto out; + } + + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown voice call URC action: %s", str); + +out: + g_free (str); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +/* + * <CR><LF>MISSED_CALL: 11:01AM 07712345678<CR><LF> + */ +GRegex * +mm_simtech_get_missed_call_urc_regex (void) +{ + return g_regex_new ("\\r\\nMISSED_CALL:\\s*(.+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +gboolean +mm_simtech_parse_missed_call_urc (GMatchInfo *match_info, + gchar **details, + GError **error) +{ + gchar *str; + + str = mm_get_string_unquoted_from_match_info (match_info, 1); + if (!str) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't read missed call URC details"); + return FALSE; + } + + *details = str; + return TRUE; +} + +/*****************************************************************************/ + +/* + * Using TWO <CR> instead of one... + * <CR><CR><LF>+CRING: VOICE<CR><CR><LF> + */ +GRegex * +mm_simtech_get_cring_urc_regex (void) +{ + return g_regex_new ("(?:\\r)+\\n\\+CRING:\\s*(\\S+)(?:\\r)+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +/*****************************************************************************/ + +/* + * <CR><CR><LF>+RXDTMF: 8<CR><CR><LF> + * <CR><CR><LF>+RXDTMF: *<CR><CR><LF> + * <CR><CR><LF>+RXDTMF: 7<CR><CR><LF> + * + * Note! using TWO <CR> instead of one... + */ +GRegex * +mm_simtech_get_rxdtmf_urc_regex (void) +{ + return g_regex_new ("(?:\\r)+\\n\\+RXDTMF:\\s*([0-9A-D\\*\\#])(?:\\r)+\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} diff --git a/src/plugins/simtech/mm-modem-helpers-simtech.h b/src/plugins/simtech/mm-modem-helpers-simtech.h new file mode 100644 index 00000000..1949d2e7 --- /dev/null +++ b/src/plugins/simtech/mm-modem-helpers-simtech.h @@ -0,0 +1,67 @@ +/* -*- 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, + gpointer log_object, + GList **out_list, + GError **error); +void mm_simtech_call_info_list_free (GList *call_info_list); + +/*****************************************************************************/ +/* VOICE CALL URC helpers */ + +GRegex *mm_simtech_get_voice_call_urc_regex (void); +gboolean mm_simtech_parse_voice_call_urc (GMatchInfo *match_info, + gboolean *start_or_stop, + guint *duration, + GError **error); + +/*****************************************************************************/ +/* MISSED_CALL URC helpers */ + +GRegex *mm_simtech_get_missed_call_urc_regex (void); +gboolean mm_simtech_parse_missed_call_urc (GMatchInfo *match_info, + gchar **details, + GError **error); + +/*****************************************************************************/ +/* Non-standard CRING URC helpers */ + +GRegex *mm_simtech_get_cring_urc_regex (void); + +/*****************************************************************************/ +/* +RXDTMF URC helpers */ + +GRegex *mm_simtech_get_rxdtmf_urc_regex (void); + +#endif /* MM_MODEM_HELPERS_SIMTECH_H */ diff --git a/src/plugins/simtech/mm-plugin-simtech.c b/src/plugins/simtech/mm-plugin-simtech.c new file mode 100644 index 00000000..9b4f377e --- /dev/null +++ b/src/plugins/simtech/mm-plugin-simtech.c @@ -0,0 +1,98 @@ +/* -*- 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-log-object.h" +#include "mm-plugin-simtech.h" +#include "mm-broadband-modem-simtech.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi-simtech.h" +#endif + +G_DEFINE_TYPE (MMPluginSimtech, mm_plugin_simtech, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered SimTech modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_simtech_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_simtech_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL }; + static const guint16 vendor_ids[] = { 0x1e0e, /* A-Link (for now) */ + 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_SIMTECH, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + NULL)); +} + +static void +mm_plugin_simtech_init (MMPluginSimtech *self) +{ +} + +static void +mm_plugin_simtech_class_init (MMPluginSimtechClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/simtech/mm-plugin-simtech.h b/src/plugins/simtech/mm-plugin-simtech.h new file mode 100644 index 00000000..eab8630c --- /dev/null +++ b/src/plugins/simtech/mm-plugin-simtech.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_SIMTECH_H +#define MM_PLUGIN_SIMTECH_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_SIMTECH (mm_plugin_simtech_get_type ()) +#define MM_PLUGIN_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtech)) +#define MM_PLUGIN_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtechClass)) +#define MM_IS_PLUGIN_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_SIMTECH)) +#define MM_IS_PLUGIN_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_SIMTECH)) +#define MM_PLUGIN_SIMTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtechClass)) + +typedef struct { + MMPlugin parent; +} MMPluginSimtech; + +typedef struct { + MMPluginClass parent; +} MMPluginSimtechClass; + +GType mm_plugin_simtech_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_SIMTECH_H */ diff --git a/src/plugins/simtech/mm-shared-simtech.c b/src/plugins/simtech/mm-shared-simtech.c new file mode 100644 index 00000000..99c2346e --- /dev/null +++ b/src/plugins/simtech/mm-shared-simtech.c @@ -0,0 +1,1261 @@ +/* -*- 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 <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-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 */ + +#define PRIVATE_TAG "shared-simtech-private-tag" +static GQuark private_quark; + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED, +} FeatureSupport; + +typedef struct { + /* location */ + MMIfaceModemLocation *iface_modem_location_parent; + MMModemLocationSource supported_sources; + MMModemLocationSource enabled_sources; + FeatureSupport cgps_support; + /* voice */ + MMIfaceModemVoice *iface_modem_voice_parent; + FeatureSupport cpcmreg_support; + FeatureSupport clcc_urc_support; + GRegex *clcc_urc_regex; + GRegex *voice_call_regex; + GRegex *missed_call_regex; + GRegex *cring_regex; + GRegex *rxdtmf_regex; +} Private; + +static void +private_free (Private *ctx) +{ + g_regex_unref (ctx->rxdtmf_regex); + g_regex_unref (ctx->cring_regex); + g_regex_unref (ctx->missed_call_regex); + g_regex_unref (ctx->voice_call_regex); + g_regex_unref (ctx->clcc_urc_regex); + g_slice_free (Private, ctx); +} + +static Private * +get_private (MMSharedSimtech *self) +{ + Private *priv; + + if (G_UNLIKELY (!private_quark)) + private_quark = (g_quark_from_static_string (PRIVATE_TAG)); + + priv = g_object_get_qdata (G_OBJECT (self), private_quark); + if (!priv) { + 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->cpcmreg_support = FEATURE_SUPPORT_UNKNOWN; + priv->clcc_urc_support = FEATURE_SUPPORT_UNKNOWN; + priv->clcc_urc_regex = mm_simtech_get_clcc_urc_regex (); + priv->voice_call_regex = mm_simtech_get_voice_call_urc_regex (); + priv->missed_call_regex = mm_simtech_get_missed_call_urc_regex (); + priv->cring_regex = mm_simtech_get_cring_urc_regex (); + priv->rxdtmf_regex = mm_simtech_get_rxdtmf_urc_regex (); + + /* Setup parent class' MMIfaceModemLocation and MMIfaceModemVoice */ + + 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_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; +} + +/*****************************************************************************/ +/* GPS trace received */ + +static void +trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +/*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +MMModemLocationSource +mm_shared_simtech_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize aux; + + aux = 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) aux; +} + +static void probe_gps_features (GTask *task); + +static void +cgps_test_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!mm_base_modem_at_command_finish (self, res, NULL)) + priv->cgps_support = FEATURE_NOT_SUPPORTED; + else + priv->cgps_support = FEATURE_SUPPORTED; + + probe_gps_features (task); +} + +static void +probe_gps_features (GTask *task) +{ + MMSharedSimtech *self; + MMModemLocationSource sources; + Private *priv; + + self = MM_SHARED_SIMTECH (g_task_get_source_object (task)); + priv = get_private (self); + + /* Need to check if CGPS supported... */ + if (priv->cgps_support == FEATURE_SUPPORT_UNKNOWN) { + mm_base_modem_at_command (MM_BASE_MODEM (self), "+CGPS=?", 3, TRUE, (GAsyncReadyCallback) cgps_test_ready, task); + return; + } + + /* All GPS features probed */ + + /* Recover parent sources */ + sources = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + if (priv->cgps_support == FEATURE_SUPPORTED) { + mm_obj_dbg (self, "GPS commands supported: GPS capabilities enabled"); + + /* We only flag as supported by this implementation those sources not already + * supported by the parent implementation */ + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) + priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA; + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW)) + priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW; + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) + priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED; + + sources |= priv->supported_sources; + + /* Add handler for the NMEA traces in the GPS data port */ + mm_port_serial_gps_add_trace_handler (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)), + (MMPortSerialGpsTraceFn)trace_received, + self, + NULL); + } else + mm_obj_dbg (self, "no GPS command supported: no GPS capabilities"); + + g_task_return_int (task, (gssize) sources); + g_object_unref (task); +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + sources = priv->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. If we don't have any GPS port, we're done */ + if (!mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) { + mm_obj_dbg (self, "no GPS data port found: no GPS capabilities"); + g_task_return_int (task, sources); + g_object_unref (task); + return; + } + + /* Cache sources supported by the parent */ + g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL); + + /* Probe all GPS features */ + probe_gps_features (task); +} + +void +mm_shared_simtech_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + priv = get_private (MM_SHARED_SIMTECH (self)); + task = g_task_new (self, NULL, callback, user_data); + + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->load_capabilities); + g_assert (priv->iface_modem_location_parent->load_capabilities_finish); + + priv->iface_modem_location_parent->load_capabilities (self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + task); +} + +/*****************************************************************************/ +/* Disable location gathering (Location interface) */ + +gboolean +mm_shared_simtech_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +disable_cgps_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource source; + Private *priv; + GError *error = NULL; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + mm_base_modem_at_command_finish (self, res, &error); + + /* Only use the GPS port in NMEA/RAW setups */ + source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task)); + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMPortSerialGps *gps_port; + + /* Even if we get an error here, we try to close the GPS port */ + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + if (error) + g_task_return_error (task, error); + else { + priv->enabled_sources &= ~source; + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +parent_disable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + g_assert (priv->iface_modem_location_parent); + if (!priv->iface_modem_location_parent->disable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_simtech_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMModemLocationSource enabled_sources; + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_location_parent); + + /* Only consider request if it applies to one of the sources we are + * supporting, otherwise run parent disable */ + if (!(priv->supported_sources & source)) { + /* If disabling implemented by the parent, run it. */ + if (priv->iface_modem_location_parent->disable_location_gathering && + priv->iface_modem_location_parent->disable_location_gathering_finish) { + priv->iface_modem_location_parent->disable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_disable_location_gathering_ready, + task); + return; + } + /* Otherwise, we're done */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* We only expect GPS sources here */ + g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)); + + /* Flag as disabled to see how many others we would have left enabled */ + enabled_sources = priv->enabled_sources; + enabled_sources &= ~source; + + /* If there are still GPS-related sources enabled, do nothing else */ + if (enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + priv->enabled_sources &= ~source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Stop GPS engine if all GPS-related sources are disabled */ + g_assert (priv->cgps_support == FEATURE_SUPPORTED); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CGPS=0", + 10, + FALSE, + (GAsyncReadyCallback) disable_cgps_ready, + task); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +gboolean +mm_shared_simtech_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +enable_cgps_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource source; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Only use the GPS port in NMEA/RAW setups */ + source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task)); + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMPortSerialGps *gps_port; + + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (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"); + g_object_unref (task); + return; + } + } + + priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + g_assert (priv->iface_modem_location_parent); + if (!priv->iface_modem_location_parent->enable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_simtech_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); + + priv = get_private (MM_SHARED_SIMTECH (self)); + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->enable_location_gathering); + g_assert (priv->iface_modem_location_parent->enable_location_gathering_finish); + + /* Only consider request if it applies to one of the sources we are + * supporting, otherwise run parent enable */ + if (!(priv->supported_sources & source)) { + priv->iface_modem_location_parent->enable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); + return; + } + + /* We only expect GPS sources here */ + g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)); + + /* If GPS already started, store new flag and we're done */ + if (priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + g_assert (priv->cgps_support == FEATURE_SUPPORTED); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CGPS=1,1", + 10, + FALSE, + (GAsyncReadyCallback) enable_cgps_ready, + task); +} + +/*****************************************************************************/ +/* 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_obj_dbg (self, "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_obj_dbg (self, "%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_obj_dbg (self, "%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_obj_warn (self, "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_obj_warn (self, "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_obj_warn (self, "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_obj_warn (self, "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, self, &call_info_list, &error)) { + mm_obj_warn (self, "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 +missed_call_urc_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMSharedSimtech *self) +{ + GError *error = NULL; + gchar *details = NULL; + + if (!mm_simtech_parse_missed_call_urc (match_info, &details, &error)) { + mm_obj_warn (self, "couldn't parse missed call URC: %s", error->message); + g_error_free (error); + return; + } + + mm_obj_dbg (self, "missed call reported: %s", details); + g_free (details); +} + +static void +voice_call_urc_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMSharedSimtech *self) +{ + GError *error = NULL; + gboolean start_or_stop = FALSE; /* start = TRUE, stop = FALSE */ + guint duration = 0; + + if (!mm_simtech_parse_voice_call_urc (match_info, &start_or_stop, &duration, &error)) { + mm_obj_warn (self, "couldn't parse voice call URC: %s", error->message); + g_error_free (error); + return; + } + + if (start_or_stop) { + mm_obj_dbg (self, "voice call started"); + return; + } + + if (duration) { + mm_obj_dbg (self, "voice call finished (duration: %us)", duration); + return; + } + + mm_obj_dbg (self, "voice call finished"); +} + +static void +cring_urc_received (MMPortSerialAt *port, + GMatchInfo *info, + MMSharedSimtech *self) +{ + MMCallInfo call_info; + g_autofree gchar *str = NULL; + + /* We could have "VOICE" or "DATA". Now consider only "VOICE" */ + str = mm_get_string_unquoted_from_match_info (info, 1); + mm_obj_dbg (self, "ringing (%s)", str); + + call_info.index = 0; + call_info.direction = MM_CALL_DIRECTION_INCOMING; + call_info.state = MM_CALL_STATE_RINGING_IN; + call_info.number = NULL; + + mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); +} + +static void +rxdtmf_urc_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMSharedSimtech *self) +{ + g_autofree gchar *dtmf = NULL; + + dtmf = g_match_info_fetch (match_info, 1); + mm_obj_dbg (self, "received DTMF: %s", dtmf); + /* call index unknown */ + mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf); +} + +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)); + + 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; + + if (priv->clcc_urc_support == FEATURE_SUPPORTED) + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->clcc_urc_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)clcc_urc_received : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->voice_call_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)voice_call_urc_received : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->missed_call_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)missed_call_urc_received : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->cring_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)cring_urc_received : NULL, + enable ? self : NULL, + NULL); + + mm_port_serial_at_add_unsolicited_msg_handler (ports[i], + priv->rxdtmf_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)rxdtmf_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_obj_warn (self, "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_obj_warn (self, "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); +} + +/*****************************************************************************/ +/* In-call audio channel setup/cleanup */ + +gboolean +mm_shared_simtech_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + MMPort **audio_port, /* optional */ + MMCallAudioFormat **audio_format, /* optional */ + GError **error) +{ + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + if (!g_task_propagate_boolean (G_TASK (res), error)) + return FALSE; + + if (audio_format) + *audio_format = NULL; + + if (audio_port) { + if (priv->cpcmreg_support == FEATURE_SUPPORTED) + *audio_port = MM_PORT (mm_base_modem_get_port_audio (MM_BASE_MODEM (self))); + else + *audio_port = NULL; + } + + return TRUE; +} + +gboolean +mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cpcmreg_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +common_setup_cleanup_in_call_audio_channel (MMSharedSimtech *self, + gboolean setup, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + task = g_task_new (self, NULL, callback, user_data); + + /* Do nothing if CPCMREG isn't supported */ + if (priv->cpcmreg_support != FEATURE_SUPPORTED) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + setup ? "+CPCMREG=1" : "+CPCMREG=0", + 3, + FALSE, + (GAsyncReadyCallback) cpcmreg_set_ready, + task); +} + +void +mm_shared_simtech_voice_setup_in_call_audio_channel (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_in_call_audio_channel (MM_SHARED_SIMTECH (self), TRUE, callback, user_data); +} + +void +mm_shared_simtech_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_in_call_audio_channel (MM_SHARED_SIMTECH (self), FALSE, callback, user_data); +} + +/*****************************************************************************/ +/* 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 +cpcmreg_format_check_ready (MMBroadbandModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + + priv = get_private (MM_SHARED_SIMTECH (self)); + + priv->cpcmreg_support = (mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ? + FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED); + mm_obj_dbg (self, "modem %s USB audio control", (priv->cpcmreg_support == FEATURE_SUPPORTED) ? "supports" : "doesn't support"); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +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_obj_dbg (self, "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_obj_dbg (self, "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); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CPCMREG=?", + 3, + TRUE, + (GAsyncReadyCallback) cpcmreg_format_check_ready, + 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) +{ +} + +GType +mm_shared_simtech_get_type (void) +{ + static GType shared_simtech_type = 0; + + if (!G_UNLIKELY (shared_simtech_type)) { + static const GTypeInfo info = { + sizeof (MMSharedSimtech), /* class_size */ + shared_simtech_init, /* base_init */ + NULL, /* base_finalize */ + }; + + shared_simtech_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedSimtech", &info, 0); + g_type_interface_add_prerequisite (shared_simtech_type, MM_TYPE_IFACE_MODEM_LOCATION); + } + + return shared_simtech_type; +} diff --git a/src/plugins/simtech/mm-shared-simtech.h b/src/plugins/simtech/mm-shared-simtech.h new file mode 100644 index 00000000..37a221ca --- /dev/null +++ b/src/plugins/simtech/mm-shared-simtech.h @@ -0,0 +1,129 @@ +/* -*- 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_SHARED_SIMTECH_H +#define MM_SHARED_SIMTECH_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" +#include "mm-iface-modem-voice.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)) +#define MM_IS_SHARED_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_SIMTECH)) +#define MM_SHARED_SIMTECH_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_SIMTECH, MMSharedSimtech)) + +typedef struct _MMSharedSimtech MMSharedSimtech; + +struct _MMSharedSimtech { + GTypeInterface g_iface; + + /* 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); + +/*****************************************************************************/ +/* Location interface */ + +void mm_shared_simtech_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMModemLocationSource mm_shared_simtech_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); + +void mm_shared_simtech_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_disable_location_gathering_finish (MMIfaceModemLocation *self, + 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); + +void mm_shared_simtech_voice_setup_in_call_audio_channel (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + MMPort **audio_port, /* optional */ + MMCallAudioFormat **audio_format, /* optional */ + GError **error); +void mm_shared_simtech_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +#endif /* MM_SHARED_SIMTECH_H */ diff --git a/src/plugins/simtech/tests/test-modem-helpers-simtech.c b/src/plugins/simtech/tests/test-modem-helpers-simtech.c new file mode 100644 index 00000000..ba6532cc --- /dev/null +++ b/src/plugins/simtech/tests/test-modem-helpers-simtech.c @@ -0,0 +1,324 @@ +/* -*- 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-test.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) +{ + g_autoptr(GRegex) clcc_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *str = NULL; + GError *error = NULL; + GList *call_info_list = NULL; + GList *l; + gboolean result; + + 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, NULL, &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); + } + + 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, (gchar *) "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, (gchar *) "123456789" }, + { 3, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "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, (gchar *) "123456789" }, + { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_WAITING, (gchar *) "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)); +} + +/*****************************************************************************/ + +static void +common_test_voice_call_urc (const gchar *urc, + gboolean expected_start_or_stop, + guint expected_duration) +{ + g_autoptr(GRegex) voice_call_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *error = NULL; + gboolean start_or_stop = FALSE; /* start = TRUE, stop = FALSE */ + guint duration = 0; + gboolean result; + + voice_call_regex = mm_simtech_get_voice_call_urc_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (voice_call_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + result = mm_simtech_parse_voice_call_urc (match_info, &start_or_stop, &duration, &error); + g_assert_no_error (error); + g_assert (result); + + g_assert_cmpuint (expected_start_or_stop, ==, start_or_stop); + g_assert_cmpuint (expected_duration, ==, duration); +} + +static void +test_voice_call_begin_urc (void) +{ + common_test_voice_call_urc ("\r\nVOICE CALL: BEGIN\r\n", TRUE, 0); +} + +static void +test_voice_call_end_urc (void) +{ + common_test_voice_call_urc ("\r\nVOICE CALL: END\r\n", FALSE, 0); +} + +static void +test_voice_call_end_duration_urc (void) +{ + common_test_voice_call_urc ("\r\nVOICE CALL: END: 000041\r\n", FALSE, 41); +} + +/*****************************************************************************/ + +static void +common_test_missed_call_urc (const gchar *urc, + const gchar *expected_details) +{ + g_autoptr(GRegex) missed_call_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *details = NULL; + GError *error = NULL; + gboolean result; + + missed_call_regex = mm_simtech_get_missed_call_urc_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (missed_call_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + result = mm_simtech_parse_missed_call_urc (match_info, &details, &error); + g_assert_no_error (error); + g_assert (result); + + g_assert_cmpstr (expected_details, ==, details); +} + +static void +test_missed_call_urc (void) +{ + common_test_missed_call_urc ("\r\nMISSED_CALL: 11:01AM 07712345678\r\n", "11:01AM 07712345678"); +} + +/*****************************************************************************/ + +static void +common_test_cring_urc (const gchar *urc, + const gchar *expected_type) +{ + g_autoptr(GRegex) cring_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *type = NULL; + GError *error = NULL; + gboolean result; + + cring_regex = mm_simtech_get_cring_urc_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (cring_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + type = g_match_info_fetch (match_info, 1); + g_assert (type); + + g_assert_cmpstr (type, ==, expected_type); +} + +static void +test_cring_urc_two_crs (void) +{ + common_test_cring_urc ("\r\r\n+CRING: VOICE\r\r\n", "VOICE"); +} + +static void +test_cring_urc_one_cr (void) +{ + common_test_cring_urc ("\r\n+CRING: VOICE\r\n", "VOICE"); +} + +/*****************************************************************************/ + +static void +common_test_rxdtmf_urc (const gchar *urc, + const gchar *expected_str) +{ + g_autoptr(GRegex) rxdtmf_regex = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *type = NULL; + GError *error = NULL; + gboolean result; + + rxdtmf_regex = mm_simtech_get_rxdtmf_urc_regex (); + + /* Same matching logic as done in MMSerialPortAt when processing URCs! */ + result = g_regex_match_full (rxdtmf_regex, urc, -1, 0, 0, &match_info, &error); + g_assert_no_error (error); + g_assert (result); + + type = g_match_info_fetch (match_info, 1); + g_assert (type); + + g_assert_cmpstr (type, ==, expected_str); +} + +static void +test_rxdtmf_urc_two_crs (void) +{ + common_test_rxdtmf_urc ("\r\r\n+RXDTMF: 8\r\r\n", "8"); + common_test_rxdtmf_urc ("\r\r\n+RXDTMF: *\r\r\n", "*"); + common_test_rxdtmf_urc ("\r\r\n+RXDTMF: #\r\r\n", "#"); + common_test_rxdtmf_urc ("\r\r\n+RXDTMF: A\r\r\n", "A"); +} + +static void +test_rxdtmf_urc_one_cr (void) +{ + common_test_rxdtmf_urc ("\r\n+RXDTMF: 8\r\n", "8"); + common_test_rxdtmf_urc ("\r\n+RXDTMF: *\r\n", "*"); + common_test_rxdtmf_urc ("\r\n+RXDTMF: #\r\n", "#"); + common_test_rxdtmf_urc ("\r\n+RXDTMF: A\r\n", "A"); +} + +/*****************************************************************************/ + +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); + + g_test_add_func ("/MM/simtech/voicecall/urc/begin", test_voice_call_begin_urc); + g_test_add_func ("/MM/simtech/voicecall/urc/end", test_voice_call_end_urc); + g_test_add_func ("/MM/simtech/voicecall/urc/end-duration", test_voice_call_end_duration_urc); + + g_test_add_func ("/MM/simtech/missedcall/urc", test_missed_call_urc); + + g_test_add_func ("/MM/simtech/cring/urc/two-crs", test_cring_urc_two_crs); + g_test_add_func ("/MM/simtech/cring/urc/one-cr", test_cring_urc_one_cr); + + g_test_add_func ("/MM/simtech/rxdtmf/urc/two-crs", test_rxdtmf_urc_two_crs); + g_test_add_func ("/MM/simtech/rxdtmf/urc/one-cr", test_rxdtmf_urc_one_cr); + + return g_test_run (); +} |