Files
ModemManager/plugins/telit/mm-common-telit.c
T
Aleksander Morgado 74fc5baca2 core: port GRegex/GMatchInfo to use autoptr()
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.
2022-09-05 17:33:11 +00:00

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);
}