The behavior of GRegex changed in 2.73.2 once it was ported from pcre1 to pcre2. In some cases it was made more strict, which is fine, in other cases it exposed some change in how it behaves on certain matches that is not extremely clear whether it's ok or not. See https://gitlab.gnome.org/GNOME/glib/-/issues/2729 See https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/issues/601 See https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/issues/621 Either way, one thing that was assumed was that initializing all GRegex/GMatchInfo variables to NULL and making sure they're NULL before they're initialized by glib (especially the GMatchInfo) was a good and safer approach. So, whenever possible, g_autoptr() is used to cleanup the allocated GMatchInfo/GRegex variables, and otherwise, g_clear_pointer() is used to ensure that no free/unref is attempted unless the given variable is not NULL, and also so that the variable is reseted to NULL after being disposed.
374 lines
12 KiB
C
374 lines
12 KiB
C
/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es>
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "mm-common-telit.h"
|
|
#include "mm-log-object.h"
|
|
#include "mm-serial-parsers.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define TAG_GETPORTCFG_SUPPORTED "getportcfg-supported"
|
|
|
|
#define TAG_TELIT_MODEM_PORT "ID_MM_TELIT_PORT_TYPE_MODEM"
|
|
#define TAG_TELIT_AUX_PORT "ID_MM_TELIT_PORT_TYPE_AUX"
|
|
#define TAG_TELIT_NMEA_PORT "ID_MM_TELIT_PORT_TYPE_NMEA"
|
|
|
|
#define TELIT_GE910_FAMILY_PID 0x0022
|
|
|
|
/* The following number of retries of the port responsiveness
|
|
* check allows having up to 30 seconds of wait, that should
|
|
* be fine for most of the modems */
|
|
#define TELIT_PORT_CHECK_RETRIES 6
|
|
|
|
gboolean
|
|
telit_grab_port (MMPlugin *self,
|
|
MMBaseModem *modem,
|
|
MMPortProbe *probe,
|
|
GError **error)
|
|
{
|
|
MMKernelDevice *port;
|
|
MMDevice *device;
|
|
MMPortType ptype;
|
|
MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
|
|
const gchar *subsys;
|
|
|
|
port = mm_port_probe_peek_port (probe);
|
|
ptype = mm_port_probe_get_port_type (probe);
|
|
device = mm_port_probe_peek_device (probe);
|
|
subsys = mm_port_probe_get_port_subsys (probe);
|
|
|
|
/* Just skip custom port identification for subsys different than tty */
|
|
if (!g_str_equal (subsys, "tty"))
|
|
goto out;
|
|
|
|
/* AT#PORTCFG (if supported) can be used for identifying the port layout */
|
|
if (g_object_get_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED) != NULL) {
|
|
guint usbif;
|
|
|
|
usbif = (guint) mm_kernel_device_get_interface_number (port);
|
|
if (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT))) {
|
|
mm_obj_dbg (self, "AT port '%s/%s' flagged as primary",
|
|
mm_port_probe_get_port_subsys (probe),
|
|
mm_port_probe_get_port_name (probe));
|
|
pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
|
|
} else if (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_AUX_PORT))) {
|
|
mm_obj_dbg (self, "AT port '%s/%s' flagged as secondary",
|
|
mm_port_probe_get_port_subsys (probe),
|
|
mm_port_probe_get_port_name (probe));
|
|
pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY;
|
|
} else if (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT))) {
|
|
mm_obj_dbg (self, "port '%s/%s' flagged as NMEA",
|
|
mm_port_probe_get_port_subsys (probe),
|
|
mm_port_probe_get_port_name (probe));
|
|
ptype = MM_PORT_TYPE_GPS;
|
|
} else
|
|
ptype = MM_PORT_TYPE_IGNORED;
|
|
}
|
|
|
|
out:
|
|
return mm_base_modem_grab_port (modem,
|
|
port,
|
|
ptype,
|
|
pflags,
|
|
error);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Custom init */
|
|
|
|
typedef struct {
|
|
MMPortSerialAt *port;
|
|
gboolean getportcfg_done;
|
|
guint getportcfg_retries;
|
|
guint port_responsive_retries;
|
|
} TelitCustomInitContext;
|
|
|
|
gboolean
|
|
telit_custom_init_finish (MMPortProbe *probe,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|
|
|
|
static void telit_custom_init_step (GTask *task);
|
|
|
|
static gboolean
|
|
cache_port_mode (MMPortProbe *probe,
|
|
MMDevice *device,
|
|
const gchar *reply)
|
|
{
|
|
g_autoptr(GRegex) r = NULL;
|
|
g_autoptr(GMatchInfo) match_info = NULL;
|
|
GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW;
|
|
GError *error = NULL;
|
|
gboolean ret = FALSE;
|
|
guint portcfg_current;
|
|
|
|
/* #PORTCFG: <requested>,<active> */
|
|
r = g_regex_new ("#PORTCFG:\\s*(\\d+),(\\d+)", flags, 0, NULL);
|
|
g_assert (r != NULL);
|
|
|
|
if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &error))
|
|
goto out;
|
|
|
|
if (!mm_get_uint_from_match_info (match_info, 2, &portcfg_current)) {
|
|
mm_obj_dbg (probe, "unrecognized #PORTCFG <active> value");
|
|
goto out;
|
|
}
|
|
|
|
/* Reference for port configurations:
|
|
* HE910/UE910/UL865 Families Ports Arrangements User Guide
|
|
* GE910 Family Ports Arrangements User Guide
|
|
*/
|
|
switch (portcfg_current) {
|
|
case 0:
|
|
case 1:
|
|
case 4:
|
|
case 5:
|
|
case 7:
|
|
case 9:
|
|
case 10:
|
|
case 11:
|
|
g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00));
|
|
if (mm_device_get_product (device) == TELIT_GE910_FAMILY_PID)
|
|
g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x02));
|
|
else
|
|
g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x06));
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
case 6:
|
|
g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00));
|
|
break;
|
|
case 8:
|
|
case 12:
|
|
g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00));
|
|
if (mm_device_get_product (device) == TELIT_GE910_FAMILY_PID) {
|
|
g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x02));
|
|
g_object_set_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT, GUINT_TO_POINTER (0x04));
|
|
} else {
|
|
g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x06));
|
|
g_object_set_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT, GUINT_TO_POINTER (0x0a));
|
|
}
|
|
break;
|
|
default:
|
|
/* portcfg value not supported */
|
|
goto out;
|
|
}
|
|
ret = TRUE;
|
|
|
|
out:
|
|
if (error) {
|
|
mm_obj_dbg (probe, "error while matching #PORTCFG: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
getportcfg_ready (MMPortSerialAt *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
MMPortProbe *probe;
|
|
TelitCustomInitContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
probe = g_task_get_source_object (task);
|
|
|
|
response = mm_port_serial_at_command_finish (port, res, &error);
|
|
if (error) {
|
|
mm_obj_dbg (probe, "couldn't get telit port mode: '%s'", error->message);
|
|
|
|
/* If ERROR or COMMAND NOT SUPPORT occur then do not retry the
|
|
* command.
|
|
*/
|
|
if (g_error_matches (error,
|
|
MM_MOBILE_EQUIPMENT_ERROR,
|
|
MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN))
|
|
ctx->getportcfg_done = TRUE;
|
|
} else {
|
|
MMDevice *device;
|
|
|
|
device = mm_port_probe_peek_device (probe);
|
|
|
|
/* Results are cached in the parent device object */
|
|
if (g_object_get_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED) == NULL) {
|
|
mm_obj_dbg (probe, "retrieving telit port mode layout");
|
|
if (cache_port_mode (probe, device, response)) {
|
|
g_object_set_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED, GUINT_TO_POINTER (TRUE));
|
|
ctx->getportcfg_done = TRUE;
|
|
}
|
|
}
|
|
|
|
/* Port answered to #PORTCFG, so mark it as being AT already */
|
|
mm_port_probe_set_result_at (probe, TRUE);
|
|
}
|
|
|
|
if (error)
|
|
g_error_free (error);
|
|
|
|
telit_custom_init_step (task);
|
|
}
|
|
|
|
static void
|
|
telit_custom_init_context_free (TelitCustomInitContext *ctx)
|
|
{
|
|
g_object_unref (ctx->port);
|
|
g_slice_free (TelitCustomInitContext, ctx);
|
|
}
|
|
|
|
static void
|
|
telit_custom_init_step (GTask *task)
|
|
{
|
|
MMKernelDevice *port;
|
|
MMPortProbe *probe;
|
|
TelitCustomInitContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
probe = g_task_get_source_object (task);
|
|
|
|
/* If cancelled, end */
|
|
if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) {
|
|
mm_obj_dbg (probe, "no need to keep on running custom init");
|
|
goto out;
|
|
}
|
|
|
|
/* Try to get a port configuration from the modem: usb interface 00
|
|
* is always linked to an AT port
|
|
*/
|
|
port = mm_port_probe_peek_port (probe);
|
|
if (!ctx->getportcfg_done && mm_kernel_device_get_interface_number (port) == 0) {
|
|
if (ctx->getportcfg_retries == 0)
|
|
goto out;
|
|
ctx->getportcfg_retries--;
|
|
|
|
mm_port_serial_at_command (
|
|
ctx->port,
|
|
"AT#PORTCFG?",
|
|
2,
|
|
FALSE, /* raw */
|
|
FALSE, /* allow_cached */
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)getportcfg_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
out:
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void at_ready (MMPortSerialAt *port,
|
|
GAsyncResult *res,
|
|
GTask *task);
|
|
|
|
static void
|
|
wait_for_ready (GTask *task)
|
|
{
|
|
TelitCustomInitContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (ctx->port_responsive_retries == 0) {
|
|
telit_custom_init_step (task);
|
|
return;
|
|
}
|
|
ctx->port_responsive_retries--;
|
|
|
|
mm_port_serial_at_command (
|
|
ctx->port,
|
|
"AT",
|
|
5,
|
|
FALSE, /* raw */
|
|
FALSE, /* allow_cached */
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)at_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
at_ready (MMPortSerialAt *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMPortProbe *probe;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
probe = g_task_get_source_object (task);
|
|
|
|
mm_port_serial_at_command_finish (port, res, &error);
|
|
if (error) {
|
|
/* On a timeout or send error, wait */
|
|
if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT) ||
|
|
g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED)) {
|
|
wait_for_ready (task);
|
|
return;
|
|
}
|
|
/* On an unknown error, make it fatal */
|
|
if (!mm_serial_parser_v1_is_known_error (error)) {
|
|
mm_obj_warn (probe, "custom port initialization logic failed: %s", error->message);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* When successful mark the port as AT and continue checking #PORTCFG */
|
|
mm_obj_dbg (probe, "port is AT");
|
|
mm_port_probe_set_result_at (probe, TRUE);
|
|
telit_custom_init_step (task);
|
|
}
|
|
|
|
void
|
|
telit_custom_init (MMPortProbe *probe,
|
|
MMPortSerialAt *port,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
TelitCustomInitContext *ctx;
|
|
GTask *task;
|
|
gboolean wait_needed;
|
|
|
|
ctx = g_slice_new (TelitCustomInitContext);
|
|
ctx->port = g_object_ref (port);
|
|
ctx->getportcfg_done = FALSE;
|
|
ctx->getportcfg_retries = 3;
|
|
ctx->port_responsive_retries = TELIT_PORT_CHECK_RETRIES;
|
|
task = g_task_new (probe, cancellable, callback, user_data);
|
|
g_task_set_check_cancellable (task, FALSE);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)telit_custom_init_context_free);
|
|
|
|
/* Some Telit modems require an initial delay for the ports to be responsive
|
|
* If no explicit tag is present, the modem does not need this step
|
|
* and can directly look for #PORTCFG support */
|
|
wait_needed = mm_kernel_device_get_global_property_as_boolean (mm_port_probe_peek_port (probe),
|
|
"ID_MM_TELIT_PORT_DELAY");
|
|
if (wait_needed) {
|
|
mm_obj_dbg (probe, "Start polling for port responsiveness");
|
|
wait_for_ready (task);
|
|
return;
|
|
}
|
|
|
|
telit_custom_init_step (task);
|
|
}
|