Files
ModemManager/plugins/via/mm-broadband-modem-via.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

544 lines
21 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) 2012 Red Hat, Inc.
*/
#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 "mm-log-object.h"
#include "mm-modem-helpers.h"
#include "mm-errors-types.h"
#include "mm-base-modem-at.h"
#include "mm-broadband-modem-via.h"
#include "mm-iface-modem-cdma.h"
#include "mm-iface-modem.h"
static void iface_modem_cdma_init (MMIfaceModemCdma *iface);
static MMIfaceModemCdma *iface_modem_cdma_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemVia, mm_broadband_modem_via, MM_TYPE_BROADBAND_MODEM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init))
struct _MMBroadbandModemViaPrivate {
/* Regex for signal quality related notifications */
GRegex *hrssilvl_regex; /* EVDO signal strength */
/* Regex for other notifications to ignore */
GRegex *mode_regex; /* Access technology change */
GRegex *dosession_regex; /* EVDO data dormancy */
GRegex *simst_regex;
GRegex *vpon_regex;
GRegex *creg_regex;
GRegex *vrom_regex; /* Roaming indicator (reportedly unreliable) */
GRegex *vser_regex;
GRegex *ciev_regex;
GRegex *vpup_regex;
};
/*****************************************************************************/
/* Setup registration checks (CDMA interface) */
typedef struct {
gboolean skip_qcdm_call_manager_step;
gboolean skip_qcdm_hdr_step;
gboolean skip_at_cdma_service_status_step;
gboolean skip_at_cdma1x_serving_system_step;
gboolean skip_detailed_registration_state;
} SetupRegistrationChecksResults;
static gboolean
setup_registration_checks_finish (MMIfaceModemCdma *self,
GAsyncResult *res,
gboolean *skip_qcdm_call_manager_step,
gboolean *skip_qcdm_hdr_step,
gboolean *skip_at_cdma_service_status_step,
gboolean *skip_at_cdma1x_serving_system_step,
gboolean *skip_detailed_registration_state,
GError **error)
{
SetupRegistrationChecksResults *results;
results = g_task_propagate_pointer (G_TASK (res), error);
if (!results)
return FALSE;
*skip_qcdm_call_manager_step = results->skip_qcdm_call_manager_step;
*skip_qcdm_hdr_step = results->skip_qcdm_hdr_step;
*skip_at_cdma_service_status_step = results->skip_at_cdma_service_status_step;
*skip_at_cdma1x_serving_system_step = results->skip_at_cdma1x_serving_system_step;
*skip_detailed_registration_state = results->skip_detailed_registration_state;
g_free (results);
return TRUE;
}
static void
parent_setup_registration_checks_ready (MMIfaceModemCdma *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
SetupRegistrationChecksResults *results;
results = g_new0 (SetupRegistrationChecksResults, 1);
if (!iface_modem_cdma_parent->setup_registration_checks_finish (self,
res,
&results->skip_qcdm_call_manager_step,
&results->skip_qcdm_hdr_step,
&results->skip_at_cdma_service_status_step,
&results->skip_at_cdma1x_serving_system_step,
&results->skip_detailed_registration_state,
&error)) {
g_free (results);
g_task_return_error (task, error);
} else {
/* Skip +CSS */
results->skip_at_cdma1x_serving_system_step = TRUE;
/* Skip +CAD */
results->skip_at_cdma_service_status_step = TRUE;
/* Force to always use the detailed registration checks, as we have
* ^SYSINFO for that */
results->skip_detailed_registration_state = FALSE;
g_task_return_pointer (task, results, g_free);
}
g_object_unref (task);
}
static void
setup_registration_checks (MMIfaceModemCdma *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* Run parent's checks first */
iface_modem_cdma_parent->setup_registration_checks (
self,
(GAsyncReadyCallback)parent_setup_registration_checks_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Detailed registration state (CDMA interface) */
typedef struct {
MMModemCdmaRegistrationState detailed_cdma1x_state;
MMModemCdmaRegistrationState detailed_evdo_state;
} DetailedRegistrationStateResults;
static gboolean
get_detailed_registration_state_finish (MMIfaceModemCdma *self,
GAsyncResult *res,
MMModemCdmaRegistrationState *detailed_cdma1x_state,
MMModemCdmaRegistrationState *detailed_evdo_state,
GError **error)
{
g_autofree DetailedRegistrationStateResults *results = NULL;
results = g_task_propagate_pointer (G_TASK (res), error);
if (!results)
return FALSE;
*detailed_cdma1x_state = results->detailed_cdma1x_state;
*detailed_evdo_state = results->detailed_evdo_state;
return TRUE;
}
static void
sysinfo_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
DetailedRegistrationStateResults *ctx;
g_autofree DetailedRegistrationStateResults *results = NULL;
const gchar *response;
g_autoptr(GRegex) r = NULL;
g_autoptr(GMatchInfo) match_info = NULL;
MMModemCdmaRegistrationState reg_state;
guint val = 0;
ctx = g_task_get_task_data (task);
/* Set input detailed states as fallback */
results = g_memdup (ctx, sizeof (*ctx));
/* If error, leave superclass' reg state alone if AT^SYSINFO isn't supported. */
response = mm_base_modem_at_command_finish (self, res, NULL);
if (!response)
goto out;
response = mm_strip_tag (response, "^SYSINFO:");
/* Format is "<srv_status>,<srv_domain>,<roam_status>,<sys_mode>,<sim_state>" */
r = g_regex_new ("\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (r != NULL);
/* Try to parse the results */
g_regex_match (r, response, 0, &match_info);
if (g_match_info_get_match_count (match_info) < 6) {
mm_obj_warn (self, "failed to parse ^SYSINFO response: '%s'", response);
goto out;
}
/* At this point the generic code already knows we've been registered */
reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
if (mm_get_uint_from_match_info (match_info, 1, &val)) {
if (val == 2) {
/* Service available, check roaming state */
val = 0;
if (mm_get_uint_from_match_info (match_info, 3, &val)) {
if (val == 0)
reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
else if (val == 1)
reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
}
}
}
/* Check service type */
val = 0;
if (mm_get_uint_from_match_info (match_info, 4, &val)) {
if (val == 2) /* CDMA */
results->detailed_cdma1x_state = reg_state;
else if (val == 4) /* HDR */
results->detailed_evdo_state = reg_state;
else if (val == 8) { /* Hybrid */
results->detailed_cdma1x_state = reg_state;
results->detailed_evdo_state = reg_state;
}
} else {
/* Say we're registered to something even though sysmode parsing failed */
mm_obj_dbg (self, "SYSMODE parsing failed: assuming registered at least in CDMA1x");
results->detailed_cdma1x_state = reg_state;
}
out:
g_task_return_pointer (task, g_steal_pointer (&results), g_free);
g_object_unref (task);
}
static void
get_detailed_registration_state (MMIfaceModemCdma *self,
MMModemCdmaRegistrationState cdma1x_state,
MMModemCdmaRegistrationState evdo_state,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
DetailedRegistrationStateResults *ctx;
/* Setup context */
ctx = g_new0 (DetailedRegistrationStateResults, 1);
ctx->detailed_cdma1x_state = cdma1x_state;
ctx->detailed_evdo_state = evdo_state;
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),
"^SYSINFO",
3,
FALSE,
(GAsyncReadyCallback)sysinfo_ready,
task);
}
/*****************************************************************************/
/* Setup/Cleanup unsolicited events (CDMA interface) */
static void
handle_evdo_quality_change (MMPortSerialAt *port,
GMatchInfo *match_info,
MMBroadbandModemVia *self)
{
guint quality = 0;
if (mm_get_uint_from_match_info (match_info, 1, &quality)) {
quality = MM_CLAMP_HIGH (quality, 100);
mm_obj_dbg (self, "EVDO signal quality: %u", quality);
mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
}
}
static void
set_unsolicited_events_handlers (MMBroadbandModemVia *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;
/* Signal quality related */
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->hrssilvl_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)handle_evdo_quality_change : NULL,
enable ? self : NULL,
NULL);
}
}
static gboolean
modem_cdma_setup_cleanup_unsolicited_events_finish (MMIfaceModemCdma *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
parent_cdma_setup_unsolicited_events_ready (MMIfaceModemCdma *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_cdma_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_VIA (self), TRUE);
g_task_return_boolean (task, TRUE);
}
g_object_unref (task);
}
static void
modem_cdma_setup_unsolicited_events (MMIfaceModemCdma *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* Chain up parent's setup */
iface_modem_cdma_parent->setup_unsolicited_events (
self,
(GAsyncReadyCallback)parent_cdma_setup_unsolicited_events_ready,
g_task_new (self, NULL, callback, user_data));
}
static void
parent_cdma_cleanup_unsolicited_events_ready (MMIfaceModemCdma *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_cdma_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_cdma_cleanup_unsolicited_events (MMIfaceModemCdma *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* Our own cleanup first */
set_unsolicited_events_handlers (MM_BROADBAND_MODEM_VIA (self), FALSE);
/* And now chain up parent's cleanup */
iface_modem_cdma_parent->cleanup_unsolicited_events (
self,
(GAsyncReadyCallback)parent_cdma_cleanup_unsolicited_events_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Setup ports (Broadband modem class) */
static void
set_ignored_unsolicited_events_handlers (MMBroadbandModemVia *self)
{
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;
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->mode_regex,
NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->dosession_regex,
NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->simst_regex,
NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->vpon_regex,
NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->creg_regex,
NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->vrom_regex,
NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->vser_regex,
NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->ciev_regex,
NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->vpup_regex,
NULL, NULL, NULL);
}
}
static void
setup_ports (MMBroadbandModem *self)
{
/* Call parent's setup ports first always */
MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_via_parent_class)->setup_ports (self);
/* Unsolicited messages to always ignore */
set_ignored_unsolicited_events_handlers (MM_BROADBAND_MODEM_VIA (self));
/* Now reset the unsolicited messages we'll handle when enabled */
set_unsolicited_events_handlers (MM_BROADBAND_MODEM_VIA (self), FALSE);
}
/*****************************************************************************/
MMBroadbandModemVia *
mm_broadband_modem_via_new (const gchar *device,
const gchar **drivers,
const gchar *plugin,
guint16 vendor_id,
guint16 product_id)
{
return g_object_new (MM_TYPE_BROADBAND_MODEM_VIA,
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,
NULL);
}
static void
mm_broadband_modem_via_init (MMBroadbandModemVia *self)
{
/* Initialize private data */
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
MM_TYPE_BROADBAND_MODEM_VIA,
MMBroadbandModemViaPrivate);
/* Prepare regular expressions to setup */
self->priv->hrssilvl_regex = g_regex_new ("\\r\\n\\^HRSSILVL:(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->mode_regex = g_regex_new ("\\r\\n\\^MODE:(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->dosession_regex = g_regex_new ("\\r\\n\\+DOSESSION:(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->simst_regex = g_regex_new ("\\r\\n\\^SIMST:(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->simst_regex = g_regex_new ("\\r\\n\\+VPON:(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->creg_regex = g_regex_new ("\\r\\n\\+CREG:(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->vrom_regex = g_regex_new ("\\r\\n\\+VROM:(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->vser_regex = g_regex_new ("\\r\\n\\+VSER:(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->ciev_regex = g_regex_new ("\\r\\n\\+CIEV:(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->vpup_regex = g_regex_new ("\\r\\n\\+VPUP:(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
}
static void
finalize (GObject *object)
{
MMBroadbandModemVia *self = MM_BROADBAND_MODEM_VIA (object);
g_regex_unref (self->priv->hrssilvl_regex);
g_regex_unref (self->priv->mode_regex);
g_regex_unref (self->priv->dosession_regex);
g_regex_unref (self->priv->simst_regex);
g_regex_unref (self->priv->simst_regex);
g_regex_unref (self->priv->creg_regex);
g_regex_unref (self->priv->vrom_regex);
g_regex_unref (self->priv->vser_regex);
g_regex_unref (self->priv->ciev_regex);
g_regex_unref (self->priv->vpup_regex);
G_OBJECT_CLASS (mm_broadband_modem_via_parent_class)->finalize (object);
}
static void
iface_modem_cdma_init (MMIfaceModemCdma *iface)
{
iface_modem_cdma_parent = g_type_interface_peek_parent (iface);
iface->setup_unsolicited_events = modem_cdma_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
iface->cleanup_unsolicited_events = modem_cdma_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
iface->setup_registration_checks = setup_registration_checks;
iface->setup_registration_checks_finish = setup_registration_checks_finish;
iface->get_detailed_registration_state = get_detailed_registration_state;
iface->get_detailed_registration_state_finish = get_detailed_registration_state_finish;
}
static void
mm_broadband_modem_via_class_init (MMBroadbandModemViaClass *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 (MMBroadbandModemViaPrivate));
object_class->finalize = finalize;
broadband_modem_class->setup_ports = setup_ports;
}