diff options
author | Dan Williams <dan@ioncontrol.co> | 2025-05-19 09:05:00 -0500 |
---|---|---|
committer | Dan Williams <dan@ioncontrol.co> | 2025-05-19 09:05:00 -0500 |
commit | ba8633e48ac7d1632f2c9bc108d420b2a56bfb56 (patch) | |
tree | 5ce4289b44fc7186c3ff5ce5e9b9c82a98cbb38f | |
parent | ec218e7052b7fd85302d1f5c9b01086ef3e562d9 (diff) | |
parent | 6a4fdbba428bc0a11f2e53af87648cdf6691e3e2 (diff) |
Merge request !1354 from 'dp/le910q-ecm-upstream'
Add ECM support for Telit LE910Q1
https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/merge_requests/1354
-rw-r--r-- | src/mm-broadband-modem.c | 29 | ||||
-rw-r--r-- | src/mm-modem-helpers.c | 87 | ||||
-rw-r--r-- | src/mm-modem-helpers.h | 13 | ||||
-rw-r--r-- | src/plugins/meson.build | 1 | ||||
-rw-r--r-- | src/plugins/telit/77-mm-telit-port-types.rules | 12 | ||||
-rw-r--r-- | src/plugins/telit/mm-broadband-bearer-telit-ecm.c | 603 | ||||
-rw-r--r-- | src/plugins/telit/mm-broadband-bearer-telit-ecm.h | 50 | ||||
-rw-r--r-- | src/plugins/telit/mm-broadband-modem-telit.c | 145 | ||||
-rw-r--r-- | src/tests/test-modem-helpers.c | 44 |
9 files changed, 971 insertions, 13 deletions
diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c index f164be01..41159a6c 100644 --- a/src/mm-broadband-modem.c +++ b/src/mm-broadband-modem.c @@ -202,6 +202,8 @@ struct _MMBroadbandModemPrivate { MM3gppCmerMode modem_cmer_disable_mode; MM3gppCmerInd modem_cmer_ind; gboolean modem_cgerep_support_checked; + MM3gppCgerepMode modem_cgerep_enable_mode; + MM3gppCgerepMode modem_cgerep_disable_mode; gboolean modem_cgerep_supported; MMFlowControl flow_control; @@ -3727,20 +3729,33 @@ cgerep_format_check_ready (MMBroadbandModem *self, GAsyncResult *res, GTask *task) { - GError *error = NULL; - const gchar *result; + MM3gppCgerepMode supported_modes = MM_3GPP_CGEREP_MODE_NONE; + GError *error = NULL; + const gchar *result; + gchar *aux; result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); - if (!result) { + if (error || !mm_3gpp_parse_cgerep_test_response (result, self, &supported_modes, &error)) { mm_obj_dbg (self, "+CGEREP check failed: %s", error->message); mm_obj_dbg (self, "packet domain event reporting is unsupported"); g_error_free (error); goto out; } - mm_obj_dbg (self, "packet domain event reporting is supported"); + aux = mm_3gpp_cgerep_mode_build_string_from_mask (supported_modes); + mm_obj_dbg (self, "supported +CGEREP modes: %s", aux); + g_free (aux); + self->priv->modem_cgerep_supported = TRUE; + if (supported_modes & MM_3GPP_CGEREP_MODE_BUFFER_URCS_IF_LINK_RESERVED) + self->priv->modem_cgerep_enable_mode = MM_3GPP_CGEREP_MODE_BUFFER_URCS_IF_LINK_RESERVED; + else if (supported_modes & MM_3GPP_CGEREP_MODE_DISCARD_URCS_IF_LINK_RESERVED) + self->priv->modem_cgerep_enable_mode = MM_3GPP_CGEREP_MODE_DISCARD_URCS_IF_LINK_RESERVED; + + if (supported_modes & MM_3GPP_CGEREP_MODE_DISCARD_URCS) + self->priv->modem_cgerep_disable_mode = MM_3GPP_CGEREP_MODE_DISCARD_URCS; + out: /* go on with remaining checks */ check_and_setup_3gpp_urc_support (task); @@ -4081,7 +4096,7 @@ modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *_self, ctx->cmer_command = mm_3gpp_build_cmer_set_request (self->priv->modem_cmer_enable_mode, self->priv->modem_cmer_ind); if (self->priv->modem_cgerep_support_checked && self->priv->modem_cgerep_supported) - ctx->cgerep_command = g_strdup ("+CGEREP=2"); + ctx->cgerep_command = mm_3gpp_build_cgerep_set_request (self->priv->modem_cgerep_enable_mode); run_unsolicited_events_setup (task); } @@ -4106,7 +4121,7 @@ modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *_self, ctx->cmer_command = mm_3gpp_build_cmer_set_request (self->priv->modem_cmer_disable_mode, MM_3GPP_CMER_IND_NONE); if (self->priv->modem_cgerep_support_checked && self->priv->modem_cgerep_supported) - ctx->cgerep_command = g_strdup ("+CGEREP=0"); + ctx->cgerep_command = mm_3gpp_build_cgerep_set_request (self->priv->modem_cgerep_disable_mode); run_unsolicited_events_setup (task); } @@ -14215,6 +14230,8 @@ mm_broadband_modem_init (MMBroadbandModem *self) self->priv->modem_cmer_enable_mode = MM_3GPP_CMER_MODE_NONE; self->priv->modem_cmer_disable_mode = MM_3GPP_CMER_MODE_NONE; self->priv->modem_cmer_ind = MM_3GPP_CMER_IND_NONE; + self->priv->modem_cgerep_enable_mode = MM_3GPP_CGEREP_MODE_NONE; + self->priv->modem_cgerep_disable_mode = MM_3GPP_CGEREP_MODE_NONE; self->priv->flow_control = MM_FLOW_CONTROL_NONE; self->priv->initial_eps_bearer_cid = -1; } diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c index e1450a05..f56fe5ac 100644 --- a/src/mm-modem-helpers.c +++ b/src/mm-modem-helpers.c @@ -3590,7 +3590,6 @@ mm_3gpp_parse_cmer_test_response (const gchar *response, GError *inner_error = NULL; GArray *array_supported_modes = NULL; GArray *array_supported_inds = NULL; - gchar *aux = NULL; gboolean ret = FALSE; MM3gppCmerMode supported_modes = 0; MM3gppCmerInd supported_inds = 0; @@ -3622,12 +3621,10 @@ mm_3gpp_parse_cmer_test_response (const gchar *response, /* Modes in 1st group */ if (!(array_supported_modes = mm_parse_uint_list (split[0], &inner_error))) goto out; - g_clear_pointer (&aux, g_free); /* Ind settings in 4th group */ if (!(array_supported_inds = mm_parse_uint_list (split[3], &inner_error))) goto out; - g_clear_pointer (&aux, g_free); for (i = 0; i < array_supported_modes->len; i++) { guint mode_val; @@ -3661,7 +3658,6 @@ out: g_array_unref (array_supported_modes); if (array_supported_inds) g_array_unref (array_supported_inds); - g_clear_pointer (&aux, g_free); g_strfreev (split); @@ -3865,6 +3861,89 @@ mm_3gpp_parse_cind_read_response (const gchar *reply, } /*************************************************************************/ + +gchar * +mm_3gpp_build_cgerep_set_request (MM3gppCgerepMode mode) +{ + guint mode_val; + + if (mode == MM_3GPP_CGEREP_MODE_DISCARD_URCS) + return g_strdup ("+CGEREP=0"); + if (mode < MM_3GPP_CGEREP_MODE_DISCARD_URCS || mode > MM_3GPP_CGEREP_MODE_BUFFER_URCS_IF_LINK_RESERVED) + return NULL; + mode_val = mm_find_bit_set (mode); + + return g_strdup_printf ("+CGEREP=%u", mode_val); +} + +gboolean +mm_3gpp_parse_cgerep_test_response (const gchar *response, + gpointer log_object, + MM3gppCgerepMode *out_supported_modes, + GError **error) +{ + gchar **split; + GError *inner_error = NULL; + GArray *array_supported_modes = NULL; + gboolean ret = FALSE; + MM3gppCgerepMode supported_modes = 0; + guint i; + + /* + * AT+CGEREP=? + * +CGEREP: (0,1),(0) + * + * AT+CGEREP=? + * +CGEREP: (0,2),(0-1) + * + * AT+CGEREP=? + * +CGEREP: (0,2),(0,1) + */ + + split = mm_split_string_groups (mm_strip_tag (response, "+CGEREP:")); + if (!split) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't split +CGEREP test response in groups"); + goto out; + } + + /* We want the 1st group */ + if (g_strv_length (split) < 1) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing groups in +CGEREP test response (%u < 1)", g_strv_length (split)); + goto out; + } + + /* Modes in 1st group */ + if (!(array_supported_modes = mm_parse_uint_list (split[0], &inner_error))) + goto out; + + for (i = 0; i < array_supported_modes->len; i++) { + guint mode_val; + + mode_val = g_array_index (array_supported_modes, guint, i); + if (mode_val <= 2) + supported_modes |= (MM3gppCgerepMode) (1 << mode_val); + else + mm_obj_dbg (log_object, "unknown +CGEREP mode reported: %u", mode_val); + } + + if (out_supported_modes) + *out_supported_modes = supported_modes; + ret = TRUE; + +out: + + if (array_supported_modes) + g_array_unref (array_supported_modes); + + g_strfreev (split); + + if (inner_error) + g_propagate_error (error, inner_error); + + return ret; +} + +/*************************************************************************/ /* +CGEV indication parser * * We provide full parsing support, including parameters, for these messages: diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h index 133c7006..469492b2 100644 --- a/src/mm-modem-helpers.h +++ b/src/mm-modem-helpers.h @@ -301,6 +301,19 @@ gint mm_3gpp_cind_response_get_max (MM3gppCindResponse *r); GByteArray *mm_3gpp_parse_cind_read_response (const gchar *reply, GError **error); +/* AT+CGEREP=? (Packet Domain Event Reporting) response parser */ +typedef enum { /*< underscore_name=mm_3gpp_cgerep_mode >*/ + MM_3GPP_CGEREP_MODE_NONE = 0, + MM_3GPP_CGEREP_MODE_DISCARD_URCS = 1 << 0, + MM_3GPP_CGEREP_MODE_DISCARD_URCS_IF_LINK_RESERVED = 1 << 1, + MM_3GPP_CGEREP_MODE_BUFFER_URCS_IF_LINK_RESERVED = 1 << 2, +} MM3gppCgerepMode; +gchar *mm_3gpp_build_cgerep_set_request (MM3gppCgerepMode mode); +gboolean mm_3gpp_parse_cgerep_test_response (const gchar *reply, + gpointer log_object, + MM3gppCgerepMode *supported_modes, + GError **error); + /* +CGEV indication parser */ typedef enum { MM_3GPP_CGEV_UNKNOWN, diff --git a/src/plugins/meson.build b/src/plugins/meson.build index b1b6bb22..97f0b6a9 100644 --- a/src/plugins/meson.build +++ b/src/plugins/meson.build @@ -215,6 +215,7 @@ if plugins_shared['telit'] 'telit/mm-common-telit.c', 'telit/mm-shared.c', 'telit/mm-shared-telit.c', + 'telit/mm-broadband-bearer-telit-ecm.c' ) enums_types = 'mm-telit-enums-types' diff --git a/src/plugins/telit/77-mm-telit-port-types.rules b/src/plugins/telit/77-mm-telit-port-types.rules index 48b40f4a..206ebeb5 100644 --- a/src/plugins/telit/77-mm-telit-port-types.rules +++ b/src/plugins/telit/77-mm-telit-port-types.rules @@ -148,6 +148,18 @@ ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="02", ENV{ ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +# LE910Q1/ELS63 (RNDIS) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7020", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +# The following port is ignored since it's a diagnostic port for collecting proprietary modem traces (not QCDM) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7020", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7020", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +# LE910Q1/ELS63 (ECM) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7021", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +# The following port is ignored since it's a diagnostic port for collecting proprietary modem traces (not QCDM) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7021", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7021", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + # LM940/960 initial port delay ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{ID_MM_TTY_AT_PROBE_TRIES}="14" ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{ID_MM_TTY_AT_PROBE_TRIES}="14" diff --git a/src/plugins/telit/mm-broadband-bearer-telit-ecm.c b/src/plugins/telit/mm-broadband-bearer-telit-ecm.c new file mode 100644 index 00000000..b5d320c0 --- /dev/null +++ b/src/plugins/telit/mm-broadband-bearer-telit-ecm.c @@ -0,0 +1,603 @@ +/* -*- 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) 2025 Daniele Palmas <dnlplm@gmail.com> + */ + +#include <config.h> + +#include "mm-broadband-bearer-telit-ecm.h" +#include "mm-broadband-modem-telit.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-log.h" +#include "mm-bind.h" + +G_DEFINE_TYPE (MMBroadbandBearerTelitEcm, mm_broadband_bearer_telit_ecm, MM_TYPE_BROADBAND_BEARER) + +/*****************************************************************************/ +/* Common helper functions */ + +static gboolean +parse_ecm_read_response (const gchar *response, + guint *state, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + /* #ECM: <Did>,<State> where: + * <Did> always 0 + * <State> 0: disabled + * 1: enabled */ + r = g_regex_new ("\\#ECM:\\s*0,(\\d+)?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match (r, response, 0, &match_info)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Invalid #ECM response: %s", response); + return FALSE; + } + + if (!mm_get_uint_from_match_info (match_info, 1, state)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to match state in #ECM response: %s", response); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ +/* 3GPP Get Config */ + +typedef struct { + MMPort *data; + MMBearerIpFamily ip_family; +} GetIpConfig3gppContext; + +static void +get_ip_config_context_free (GetIpConfig3gppContext *ctx) +{ + g_object_unref (ctx->data); + g_slice_free (GetIpConfig3gppContext, ctx); +} + +static gboolean +get_ip_config_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + MMBearerIpConfig **ipv4_config, + MMBearerIpConfig **ipv6_config, + GError **error) +{ + MMBearerConnectResult *configs; + MMBearerIpConfig *ipv4, *ipv6; + + configs = g_task_propagate_pointer (G_TASK (res), error); + if (!configs) + return FALSE; + + ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs); + ipv6 = mm_bearer_connect_result_peek_ipv6_config (configs); + g_assert (ipv4 || ipv6); + if (ipv4_config && ipv4) + *ipv4_config = g_object_ref (ipv4); + if (ipv6_config && ipv6) + *ipv6_config = g_object_ref (ipv6); + + mm_bearer_connect_result_unref (configs); + return TRUE; +} + +static void +get_hwaddress_ready (MMPortNet *port, + GAsyncResult *res, + GTask *task) +{ + GetIpConfig3gppContext *ctx; + GByteArray *hwaddr; + MMBearerIpConfig *ipv4_config = NULL; + MMBearerIpConfig *ipv6_config = NULL; + GError *error = NULL; + MMBearerConnectResult *connect_result; + + ctx = g_task_get_task_data (task); + + hwaddr = mm_port_net_get_hwaddress_finish (port, res, &error); + if (!hwaddr) { + g_task_return_error (task, error); + goto out; + } + + if (ctx->ip_family & MM_BEARER_IP_FAMILY_IPV4 || + ctx->ip_family & MM_BEARER_IP_FAMILY_IPV4V6) { + ipv4_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP); + } + + if (ctx->ip_family & MM_BEARER_IP_FAMILY_IPV6 || + ctx->ip_family & MM_BEARER_IP_FAMILY_IPV4V6) { + g_autofree gchar *lladdr; + + ipv6_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); + + lladdr = g_strdup_printf ("fe80::%02x%02x:%02xff:fe%02x:%02x%02x", + hwaddr->data[0] ^ 2, hwaddr->data[1], + hwaddr->data[2], hwaddr->data[3], + hwaddr->data[4], hwaddr->data[5]); + + mm_bearer_ip_config_set_address (ipv6_config, lladdr); + mm_bearer_ip_config_set_prefix (ipv6_config, 64); + } + + if (!ipv4_config && !ipv6_config) { + error = g_error_new_literal (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't generate IP config: invalid IP family"); + g_task_return_error (task, error); + goto out; + } + + connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->data), + ipv4_config, + ipv6_config); + g_task_return_pointer (task, + connect_result, + (GDestroyNotify)mm_bearer_connect_result_unref); + +out: + g_object_unref (task); + g_clear_object (&ipv4_config); + g_clear_object (&ipv6_config); +} + +static void +get_ip_config_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + MMBearerIpFamily ip_family, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GetIpConfig3gppContext *ctx; + GTask *task; + + ctx = g_slice_new0 (GetIpConfig3gppContext); + ctx->data = g_object_ref (data); + ctx->ip_family = ip_family; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)get_ip_config_context_free); + + mm_port_net_get_hwaddress (MM_PORT_NET (ctx->data), + NULL, + (GAsyncReadyCallback) get_hwaddress_ready, + task); +} + +/*****************************************************************************/ +/* 3GPP Connect */ + +typedef struct { + MMBroadbandModem *modem; + MMPortSerialAt *primary; + MMPortSerialAt *secondary; + MMBearerIpFamily ip_family; +} ConnectContext; + +static void +connect_context_free (ConnectContext *ctx) +{ + g_clear_object (&ctx->modem); + g_clear_object (&ctx->primary); + g_clear_object (&ctx->secondary); + g_slice_free (ConnectContext, ctx); +} + +static MMBearerConnectResult * +connect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_connect_3gpp_ready (MMBroadbandBearer *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + MMBearerConnectResult *result; + + result = MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_telit_ecm_parent_class)->connect_3gpp_finish (self, res, &error); + if (result) + g_task_return_pointer (task, result, (GDestroyNotify) mm_bearer_connect_result_unref); + else + g_task_return_error (task, error); + g_object_unref (task); +} + +static void +disconnect_3gpp_ready (MMBroadbandBearer *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + gboolean result; + ConnectContext *ctx; + + result = MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp_finish (self, res, &error); + if (!result) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_telit_ecm_parent_class)->connect_3gpp ( + self, + ctx->modem, + ctx->primary, + ctx->secondary, + g_task_get_cancellable (task), + (GAsyncReadyCallback) parent_connect_3gpp_ready, + task); +} + +static void +ecm_check_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandBearer *self; + ConnectContext *ctx; + GError *error = NULL; + const gchar *response; + guint state; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!parse_ecm_read_response (response, &state, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (state) { + /* ECM is already active, disconnect first. */ + mm_obj_dbg (self, "ECM active, tearing down existing connection..."); + MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp ( + MM_BROADBAND_BEARER (self), + ctx->modem, + ctx->primary, + ctx->secondary, + NULL, /* data port */ + 0, /* This should be the cid, but #ECMD does not need that */ + (GAsyncReadyCallback) disconnect_3gpp_ready, + task); + return; + } + + /* Execute the regular connection flow if ECM is inactive. */ + MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_telit_ecm_parent_class)->connect_3gpp ( + MM_BROADBAND_BEARER (self), + ctx->modem, + ctx->primary, + ctx->secondary, + g_task_get_cancellable (task), + (GAsyncReadyCallback) parent_connect_3gpp_ready, + task); +} + +static void +connect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ConnectContext *ctx; + GTask *task; + + ctx = g_slice_new0 (ConnectContext); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->secondary = secondary ? g_object_ref (secondary) : NULL; + ctx->ip_family = mm_bearer_properties_get_ip_type (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); + mm_3gpp_normalize_ip_family (&ctx->ip_family, TRUE); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify) connect_context_free); + + /* Check whether ECM is already active */ + mm_base_modem_at_command (MM_BASE_MODEM (modem), + "#ECM?", + 3, + FALSE, /* allow_cached */ + (GAsyncReadyCallback) ecm_check_ready, + task); +} + +/*****************************************************************************/ +/* Dial context and task */ + +typedef struct { + MMPortSerialAt *primary; + guint cid; + MMPort *data; +} DialContext; + +static void +dial_task_free (DialContext *ctx) +{ + g_object_unref (ctx->primary); + g_clear_object (&ctx->data); + g_slice_free (DialContext, ctx); +} + +static GTask * +dial_task_new (MMBroadbandBearerTelitEcm *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DialContext *ctx; + GTask *task; + + ctx = g_slice_new0 (DialContext); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify) dial_task_free); + + ctx->data = mm_base_modem_get_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET); + if (!ctx->data) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to launch connection"); + g_object_unref (task); + return NULL; + } + + return task; +} + +/*****************************************************************************/ +/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ + +static MMPort * +dial_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +ecm_verify_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + DialContext *ctx; + GError *error = NULL; + const gchar *response; + + ctx = g_task_get_task_data (task); + response = mm_base_modem_at_command_finish (modem, res, &error); + + if (!response) + g_task_return_error (task, error); + else { + guint state = 0; + + if (!parse_ecm_read_response (response, &state, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (state != 1) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed ECM check, state = %u", state); + g_object_unref (task); + return; + } + + g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); + } + + g_object_unref (task); +} + +static void +ecm_activate_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (modem, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_base_modem_at_command (modem, + "#ECM?", + 3, + FALSE, /* allow_cached */ + (GAsyncReadyCallback) ecm_verify_ready, + task); +} + +static void +dial_3gpp (MMBroadbandBearer *self, + MMBaseModem *modem, + MMPortSerialAt *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + g_autofree gchar *cmd = NULL; + + task = dial_task_new (MM_BROADBAND_BEARER_TELIT_ECM (self), + MM_BROADBAND_MODEM (modem), + primary, + cid, + cancellable, + callback, + user_data); + if (!task) + return; + + cmd = g_strdup_printf ("#ECM=%u,0", cid); + mm_base_modem_at_command (modem, + cmd, + MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, + FALSE, /* allow_cached */ + (GAsyncReadyCallback) ecm_activate_ready, + task); +} + +/*****************************************************************************/ +/* 3GPP Disconnect sequence */ + +static gboolean +disconnect_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +ecm_deactivate_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (modem, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +disconnect_3gpp (MMBroadbandBearer *self, + MMBroadbandModem *modem, + MMPortSerialAt *primary, + MMPortSerialAt *secondary, + MMPort *data, + guint cid, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* #ECMD command does not support cid selection and terminates the data + * connection in every cid. */ + mm_base_modem_at_command (MM_BASE_MODEM (modem), + "#ECMD=0", + MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, + FALSE, /* allow_cached */ + (GAsyncReadyCallback) ecm_deactivate_ready, + task); +} + +/*****************************************************************************/ + +MMBaseBearer * +mm_broadband_bearer_telit_ecm_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *bearer; + GObject *source; + + source = g_async_result_get_source_object (res); + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); + g_object_unref (source); + + if (!bearer) + return NULL; + + /* Only export valid bearers */ + mm_base_bearer_export (MM_BASE_BEARER (bearer)); + + return MM_BASE_BEARER (bearer); +} + +void +mm_broadband_bearer_telit_ecm_new (MMBroadbandModemTelit *modem, + MMBearerProperties *config, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async ( + MM_TYPE_BROADBAND_BEARER_TELIT_ECM, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + MM_BASE_BEARER_MODEM, modem, + MM_BIND_TO, G_OBJECT (modem), + MM_BASE_BEARER_CONFIG, config, + NULL); +} + +static void +mm_broadband_bearer_telit_ecm_init (MMBroadbandBearerTelitEcm *self) +{ +} + +static void +mm_broadband_bearer_telit_ecm_class_init (MMBroadbandBearerTelitEcmClass *klass) +{ + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + /* No need to redefine load_connection_status, since the generic AT+CGACT? can be used */ + broadband_bearer_class->connect_3gpp = connect_3gpp; + broadband_bearer_class->connect_3gpp_finish = connect_3gpp_finish; + broadband_bearer_class->dial_3gpp = dial_3gpp; + broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; + broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; + broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; + broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp; + broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish; +} diff --git a/src/plugins/telit/mm-broadband-bearer-telit-ecm.h b/src/plugins/telit/mm-broadband-bearer-telit-ecm.h new file mode 100644 index 00000000..ab1ed78d --- /dev/null +++ b/src/plugins/telit/mm-broadband-bearer-telit-ecm.h @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2025 Daniele Palmas <dnlplm@gmail.com> + */ + +#ifndef MM_BROADBAND_BEARER_TELIT_ECM_H +#define MM_BROADBAND_BEARER_TELIT_ECM_H + +#include "mm-broadband-bearer.h" +#include "mm-broadband-modem-telit.h" + +#define MM_TYPE_BROADBAND_BEARER_TELIT_ECM (mm_broadband_bearer_telit_ecm_get_type ()) +#define MM_BROADBAND_BEARER_TELIT_ECM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_TELIT_ECM, MMBroadbandBearerTelitEcm)) +#define MM_BROADBAND_BEARER_TELIT_ECM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_TELIT_ECM, MMBroadbandBearerTelitEcmClass)) +#define MM_IS_BROADBAND_BEARER_TELIT_ECM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_TELIT_ECM)) +#define MM_IS_BROADBAND_BEARER_TELIT_ECM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_TELIT_ECM)) +#define MM_BROADBAND_BEARER_TELIT_ECM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_TELIT_ECM, MMBroadbandBearerTelitEcmClass)) + +typedef struct _MMBroadbandBearerTelitEcm MMBroadbandBearerTelitEcm; +typedef struct _MMBroadbandBearerTelitEcmClass MMBroadbandBearerTelitEcmClass; + +struct _MMBroadbandBearerTelitEcm { + MMBroadbandBearer parent; +}; + +struct _MMBroadbandBearerTelitEcmClass { + MMBroadbandBearerClass parent; +}; + +GType mm_broadband_bearer_telit_ecm_get_type (void); + +void mm_broadband_bearer_telit_ecm_new (MMBroadbandModemTelit *modem, + MMBearerProperties *properties, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +MMBaseBearer *mm_broadband_bearer_telit_ecm_new_finish (GAsyncResult *res, + GError **error); + +#endif /* MM_BROADBAND_BEARER_TELIT_ECM_H */ diff --git a/src/plugins/telit/mm-broadband-modem-telit.c b/src/plugins/telit/mm-broadband-modem-telit.c index dbdfd8b0..530c93e1 100644 --- a/src/plugins/telit/mm-broadband-modem-telit.c +++ b/src/plugins/telit/mm-broadband-modem-telit.c @@ -32,6 +32,7 @@ #include "mm-iface-modem-3gpp.h" #include "mm-iface-modem-location.h" #include "mm-broadband-modem-telit.h" +#include "mm-broadband-bearer-telit-ecm.h" #include "mm-modem-helpers-telit.h" #include "mm-telit-enums-types.h" #include "mm-shared-telit.h" @@ -67,6 +68,7 @@ struct _MMBroadbandModemTelitPrivate { guint csim_lock_timeout_id; gboolean parse_qss; MMModemLocationSource enabled_sources; + FeatureSupport ecm_support; }; typedef struct { @@ -1121,8 +1123,10 @@ response_processor_cops_ignore_at_errors (MMBaseModem *self, vid = mm_base_modem_get_vendor_id (self); pid = mm_base_modem_get_product_id (self); - if (!(vid == 0x1bc7 && (pid == 0x110a || pid == 0x110b))) { - /* AcT for non-LPWA modems would be checked by other command */ + if (!(vid == 0x1bc7 && (pid == 0x110a || pid == 0x110b || + pid == 0x7020 || pid == 0x7021))) { + /* LE910Q1/ELS63-I do not support #PSNT or +SERVICE + * AcT for non-LPWA modems would be checked by other command */ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; } @@ -1171,6 +1175,9 @@ response_processor_cops_ignore_at_errors (MMBaseModem *self, case 0: *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_GSM); return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 7: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_LTE); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; case 8: *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_LTE_CAT_M); return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; @@ -1514,6 +1521,135 @@ modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, } /*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_telit_ecm_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_telit_ecm_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + g_object_unref (task); +} + +static void +broadband_bearer_new_ready (GObject *source, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer = NULL; + GError *error = NULL; + + bearer = mm_broadband_bearer_new_finish (res, &error); + if (!bearer) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bearer, g_object_unref); + g_object_unref (task); +} + +static void +common_create_bearer (GTask *task) +{ + MMBroadbandModemTelit *self; + + self = g_task_get_source_object (task); + + switch (self->priv->ecm_support) { + case FEATURE_SUPPORTED: + mm_broadband_bearer_telit_ecm_new (self, + g_task_get_task_data (task), + NULL, /* cancellable */ + (GAsyncReadyCallback) broadband_bearer_telit_ecm_new_ready, + task); + return; + case FEATURE_NOT_SUPPORTED: + mm_broadband_bearer_new (MM_BROADBAND_MODEM (self), + g_task_get_task_data (task), + NULL, /* cancellable */ + (GAsyncReadyCallback) broadband_bearer_new_ready, + task); + return; + case FEATURE_SUPPORT_UNKNOWN: + default: + g_assert_not_reached (); + } +} + +static void +ecm_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemTelit *self = MM_BROADBAND_MODEM_TELIT (_self); + + if (!mm_base_modem_at_command_finish (_self, res, NULL)) { + mm_obj_dbg (self, "#ECM unsupported"); + self->priv->ecm_support = FEATURE_NOT_SUPPORTED; + } else { + self->priv->ecm_support = FEATURE_SUPPORTED; + } + + common_create_bearer (task); +} + +static void +modem_create_bearer (MMIfaceModem *_self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemTelit *self = MM_BROADBAND_MODEM_TELIT (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, g_object_ref (properties), g_object_unref); + + if (self->priv->ecm_support != FEATURE_SUPPORT_UNKNOWN) { + common_create_bearer (task); + return; + } + + if (!(mm_base_modem_get_vendor_id (MM_BASE_MODEM (self)) == 0x1bc7 && + mm_base_modem_get_product_id (MM_BASE_MODEM (self)) == 0x7021)) { + /* ECM supported just in LE910Q1/ELS63-I composition 0x7021 */ + self->priv->ecm_support = FEATURE_NOT_SUPPORTED; + common_create_bearer (task); + return; + } + + if (!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) { + mm_obj_dbg (self, "skipping #ECM check as no data port is available"); + self->priv->ecm_support = FEATURE_NOT_SUPPORTED; + common_create_bearer (task); + return; + } + + mm_obj_dbg (self, "checking #ECM support..."); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "#ECM=?", + 3, + TRUE, + (GAsyncReadyCallback) ecm_test_ready, + task); +} + +/*****************************************************************************/ MMBroadbandModemTelit * mm_broadband_modem_telit_new (const gchar *device, @@ -1531,7 +1667,7 @@ mm_broadband_modem_telit_new (const gchar *device, MM_BASE_MODEM_VENDOR_ID, vendor_id, MM_BASE_MODEM_PRODUCT_ID, product_id, /* Generic bearer supports AT only */ - MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, NULL); @@ -1548,6 +1684,7 @@ mm_broadband_modem_telit_init (MMBroadbandModemTelit *self) self->priv->csim_lock_state = CSIM_LOCK_STATE_UNKNOWN; self->priv->qss_status = QSS_STATUS_UNKNOWN; self->priv->parse_qss = TRUE; + self->priv->ecm_support = FEATURE_SUPPORT_UNKNOWN; } static void @@ -1582,6 +1719,8 @@ iface_modem_init (MMIfaceModemInterface *iface) iface->setup_sim_hot_swap = modem_setup_sim_hot_swap; iface->setup_sim_hot_swap_finish = modem_setup_sim_hot_swap_finish; iface->cleanup_sim_hot_swap = modem_cleanup_sim_hot_swap; + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; } static void diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c index a6755b0a..5ed5721d 100644 --- a/src/tests/test-modem-helpers.c +++ b/src/tests/test-modem-helpers.c @@ -2261,6 +2261,47 @@ test_cind_response_moto_v3m (void *f, gpointer d) } /*****************************************************************************/ +/* Test CGEREP test responses */ + +static void +test_cgerep_response (const gchar *str, + MM3gppCgerepMode expected_modes) +{ + gboolean ret; + MM3gppCgerepMode modes = MM_3GPP_CGEREP_MODE_NONE; + GError *error = NULL; + + ret = mm_3gpp_parse_cgerep_test_response (str, NULL, &modes, &error); + g_assert_no_error (error); + g_assert (ret); + + g_assert_cmpuint (modes, ==, expected_modes); +} + +static void +test_cgerep_response_telit_le910q1 (void) +{ + static const gchar *str = "+CGEREP: (0,1),(0)"; + static const MM3gppCgerepMode expected_modes = ( \ + MM_3GPP_CGEREP_MODE_DISCARD_URCS | \ + MM_3GPP_CGEREP_MODE_DISCARD_URCS_IF_LINK_RESERVED); + + test_cgerep_response (str, expected_modes); +} + +static void +test_cgerep_response_telit_ln920 (void) +{ + static const gchar *str = "+CGEREP: (0-2),(0,1)"; + static const MM3gppCgerepMode expected_modes = ( \ + MM_3GPP_CGEREP_MODE_DISCARD_URCS | \ + MM_3GPP_CGEREP_MODE_DISCARD_URCS_IF_LINK_RESERVED | \ + MM_3GPP_CGEREP_MODE_BUFFER_URCS_IF_LINK_RESERVED); + + test_cgerep_response (str, expected_modes); +} + +/*****************************************************************************/ /* Test +CGEV indication parsing */ typedef struct { @@ -5194,6 +5235,9 @@ int main (int argc, char **argv) g_test_suite_add (suite, TESTCASE (test_cind_response_linktop_lw273, NULL)); g_test_suite_add (suite, TESTCASE (test_cind_response_moto_v3m, NULL)); + g_test_suite_add (suite, TESTCASE (test_cgerep_response_telit_le910q1, NULL)); + g_test_suite_add (suite, TESTCASE (test_cgerep_response_telit_ln920, NULL)); + g_test_suite_add (suite, TESTCASE (test_cgev_indication, NULL)); g_test_suite_add (suite, TESTCASE (test_iccid_parse_quoted_swap_19_digit, NULL)); |