Files
ModemManager/plugins/quectel/mm-shared-quectel.c
Aleksander Morgado b497de325a iface-modem: common SIM event reporting logic
We no longer have separate mm_base_modem_process_sim_event() and
mm_broadband_modem_sim_hot_swap_detected() methods. The only
difference between both of them was that one of them would attempt to
cleanup the ports context associated to the SIM hot swap event logic
as soon as a swap was detected, in order to avoid queueing up multiple
such events.

The previous logic wasn't working well, though, as there could be
mixed AT+QMI or AT+MBIM devices that would also require that same
cleanup and so we didn't always know which one should have been
called.

Now we have a single mm_iface_modem_process_sim_event() method, which
will trigger the reprobe and disabling, but which will also perform
the cleanup of the SIM ports swap setup as specified by the
implementation.

So, if a plugin explicitly initializes the serial ports context for
SIM hot swap handling, it should also explicitly clean it up.

Also, the initialization of the serial ports context for SIM hot swap
handling is no longer done automatically for all modems, it will be
done only for those modems using it; i.e. the modems that explicitly
report support SIM hot swap handling using AT URCs.
2022-05-20 09:03:54 +00:00

1007 lines
36 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) 2018-2020 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-firmware.h"
#include "mm-iface-modem-location.h"
#include "mm-base-modem.h"
#include "mm-base-modem-at.h"
#include "mm-shared-quectel.h"
#include "mm-modem-helpers-quectel.h"
#if defined WITH_MBIM
#include "mm-broadband-modem-mbim.h"
#endif
/*****************************************************************************/
/* Private context */
#define PRIVATE_TAG "shared-quectel-private-tag"
static GQuark private_quark;
typedef enum {
FEATURE_SUPPORT_UNKNOWN,
FEATURE_NOT_SUPPORTED,
FEATURE_SUPPORTED,
} FeatureSupport;
typedef struct {
MMBroadbandModemClass *broadband_modem_class_parent;
MMIfaceModem *iface_modem_parent;
MMIfaceModemLocation *iface_modem_location_parent;
MMModemLocationSource provided_sources;
MMModemLocationSource enabled_sources;
FeatureSupport qgps_supported;
GRegex *qgpsurc_regex;
GRegex *qlwurc_regex;
} Private;
static void
private_free (Private *priv)
{
g_regex_unref (priv->qgpsurc_regex);
g_regex_unref (priv->qlwurc_regex);
g_slice_free (Private, priv);
}
static Private *
get_private (MMSharedQuectel *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->provided_sources = MM_MODEM_LOCATION_SOURCE_NONE;
priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE;
priv->qgps_supported = FEATURE_SUPPORT_UNKNOWN;
priv->qgpsurc_regex = g_regex_new ("\\r\\n\\+QGPSURC:.*", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
priv->qlwurc_regex = g_regex_new ("\\r\\n\\+QLWURC:.*", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_broadband_modem_class);
priv->broadband_modem_class_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_broadband_modem_class (self);
g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_location_interface);
priv->iface_modem_location_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_location_interface (self);
g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_interface);
priv->iface_modem_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_interface (self);
g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
}
return priv;
}
/*****************************************************************************/
/* Setup ports (Broadband modem class) */
void
mm_shared_quectel_setup_ports (MMBroadbandModem *self)
{
Private *priv;
MMPortSerialAt *ports[2];
guint i;
priv = get_private (MM_SHARED_QUECTEL (self));
g_assert (priv->broadband_modem_class_parent);
g_assert (priv->broadband_modem_class_parent->setup_ports);
/* Parent setup always first */
priv->broadband_modem_class_parent->setup_ports (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));
/* Enable/disable unsolicited events in given port */
for (i = 0; i < G_N_ELEMENTS (ports); i++) {
if (!ports[i])
continue;
/* Ignore +QGPSURC */
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
priv->qgpsurc_regex,
NULL, NULL, NULL);
/* Ignore +QLWURC */
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
priv->qlwurc_regex,
NULL, NULL, NULL);
}
}
/*****************************************************************************/
/* Firmware update settings loading (Firmware interface) */
MMFirmwareUpdateSettings *
mm_shared_quectel_firmware_load_update_settings_finish (MMIfaceModemFirmware *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static gboolean
quectel_is_sahara_supported (MMBaseModem *modem,
MMPort *port)
{
return mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_QUECTEL_SAHARA");
}
static gboolean
quectel_is_firehose_supported (MMBaseModem *modem,
MMPort *port)
{
return mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_QUECTEL_FIREHOSE");
}
static MMModemFirmwareUpdateMethod
quectel_get_firmware_update_methods (MMBaseModem *modem,
MMPort *port)
{
MMModemFirmwareUpdateMethod update_methods;
update_methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE;
if (quectel_is_firehose_supported (modem, port))
update_methods |= MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE;
if (quectel_is_sahara_supported (modem, port))
update_methods |= MM_MODEM_FIRMWARE_UPDATE_METHOD_SAHARA;
return update_methods;
}
static void
quectel_at_port_get_firmware_version_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
MMFirmwareUpdateSettings *update_settings;
const gchar *version;
update_settings = g_task_get_task_data (task);
version = mm_base_modem_at_command_finish (modem, res, NULL);
if (version)
mm_firmware_update_settings_set_version (update_settings, version);
g_task_return_pointer (task, g_object_ref (update_settings), g_object_unref);
g_object_unref (task);
}
#if defined WITH_MBIM
static void
quectel_mbim_port_get_firmware_version_ready (MbimDevice *device,
GAsyncResult *res,
GTask *task)
{
g_autoptr(MbimMessage) response = NULL;
guint32 version_id;
g_autofree gchar *version_str = NULL;
MMFirmwareUpdateSettings *update_settings;
update_settings = g_task_get_task_data (task);
response = mbim_device_command_finish (device, res, NULL);
if (response && mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, NULL) &&
mbim_message_qdu_quectel_read_version_response_parse (response, &version_id, &version_str, NULL)) {
mm_firmware_update_settings_set_version (update_settings, version_str);
}
g_task_return_pointer (task, g_object_ref (update_settings), g_object_unref);
g_object_unref (task);
}
#endif
static void
qfastboot_test_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
MMFirmwareUpdateSettings *update_settings;
update_settings = g_task_get_task_data (task);
/* Set update method */
if (mm_base_modem_at_command_finish (self, res, NULL)) {
mm_firmware_update_settings_set_method (update_settings, MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT);
mm_firmware_update_settings_set_fastboot_at (update_settings, "AT+QFASTBOOT");
} else
mm_firmware_update_settings_set_method (update_settings, MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE);
/* Fetch full firmware info */
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+QGMR?",
3,
FALSE,
(GAsyncReadyCallback) quectel_at_port_get_firmware_version_ready,
task);
}
static void
quectel_at_port_get_firmware_revision_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
MMFirmwareUpdateSettings *update_settings;
MMModemFirmwareUpdateMethod update_methods;
const gchar *revision;
const gchar *name;
const gchar *id;
g_autoptr(GPtrArray) ids = NULL;
GError *error = NULL;
update_settings = g_task_get_task_data (task);
update_methods = mm_firmware_update_settings_get_method (update_settings);
/* Set device ids */
ids = mm_iface_firmware_build_generic_device_ids (MM_IFACE_MODEM_FIRMWARE (self), &error);
if (error) {
mm_obj_warn (self, "failed to build generic device ids: %s", error->message);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Add device id based on modem name */
revision = mm_base_modem_at_command_finish (self, res, NULL);
if (revision && g_utf8_validate (revision, -1, NULL)) {
name = g_strndup (revision, 7);
mm_obj_dbg (self, "revision %s converted to modem name %s", revision, name);
id = (const gchar *) g_ptr_array_index (ids, 0);
g_ptr_array_insert (ids, 0, g_strdup_printf ("%s&NAME_%s", id, name));
}
mm_firmware_update_settings_set_device_ids (update_settings, (const gchar **)ids->pdata);
/* Set update methods */
if (update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) {
/* Fetch full firmware info */
mm_base_modem_at_command (self,
"+QGMR?",
3,
TRUE,
(GAsyncReadyCallback) quectel_at_port_get_firmware_version_ready,
task);
} else {
/* Check fastboot support */
mm_base_modem_at_command (self,
"AT+QFASTBOOT=?",
3,
TRUE,
(GAsyncReadyCallback) qfastboot_test_ready,
task);
}
}
void
mm_shared_quectel_firmware_load_update_settings (MMIfaceModemFirmware *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
MMPortSerialAt *at_port;
MMModemFirmwareUpdateMethod update_methods;
MMFirmwareUpdateSettings *update_settings;
#if defined WITH_MBIM
MMPortMbim *mbim;
#endif
task = g_task_new (self, NULL, callback, user_data);
at_port = mm_base_modem_peek_best_at_port (MM_BASE_MODEM (self), NULL);
if (at_port) {
update_methods = quectel_get_firmware_update_methods (MM_BASE_MODEM (self), MM_PORT (at_port));
update_settings = mm_firmware_update_settings_new (update_methods);
g_task_set_task_data (task, update_settings, g_object_unref);
/* Fetch modem name */
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CGMR",
3,
TRUE,
(GAsyncReadyCallback) quectel_at_port_get_firmware_revision_ready,
task);
return;
}
#if defined WITH_MBIM
mbim = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
if (mbim) {
g_autoptr(MbimMessage) message = NULL;
update_methods = quectel_get_firmware_update_methods (MM_BASE_MODEM (self), MM_PORT (mbim));
update_settings = mm_firmware_update_settings_new (update_methods);
/* Fetch firmware info */
g_task_set_task_data (task, update_settings, g_object_unref);
message = mbim_message_qdu_quectel_read_version_set_new (MBIM_QDU_QUECTEL_VERSION_TYPE_FW_BUILD_ID, NULL);
mbim_device_command (mm_port_mbim_peek_device (mbim),
message,
5,
NULL,
(GAsyncReadyCallback) quectel_mbim_port_get_firmware_version_ready,
task);
return;
}
#endif
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't find a port to fetch firmware info");
g_object_unref (task);
}
/*****************************************************************************/
/* "+QUSIM: 1" URC is emitted by Quectel modems after the USIM has been
* (re)initialized. We register a handler for this URC and perform a check
* for SIM swap when it is encountered. The motivation for this is to detect
* M2M eUICC profile switches. According to SGP.02 chapter 3.2.1, the eUICC
* shall trigger a REFRESH operation with eUICC reset when a new profile is
* enabled. The +QUSIM URC appears after the eUICC has restarted and can act
* as a trigger for profile switch check. This should basically be handled
* the same as a physical SIM swap, so the existing SIM hot swap mechanism
* is used.
*/
static void
quectel_qusim_check_for_sim_swap_ready (MMIfaceModem *self,
GAsyncResult *res)
{
g_autoptr(GError) error = NULL;
if (!MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap_finish (self, res, &error))
mm_obj_warn (self, "couldn't check SIM swap: %s", error->message);
else
mm_obj_dbg (self, "check SIM swap completed");
}
static void
quectel_qusim_unsolicited_handler (MMPortSerialAt *port,
GMatchInfo *match_info,
MMIfaceModem *self)
{
if (!MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap ||
!MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap_finish)
return;
mm_obj_dbg (self, "checking SIM swap");
MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap (
self,
NULL,
(GAsyncReadyCallback)quectel_qusim_check_for_sim_swap_ready,
NULL);
}
/*****************************************************************************/
/* Setup SIM hot swap context (Modem interface) */
gboolean
mm_shared_quectel_setup_sim_hot_swap_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
parent_setup_sim_hot_swap_ready (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
Private *priv;
g_autoptr(GError) error = NULL;
priv = get_private (MM_SHARED_QUECTEL (self));
if (!priv->iface_modem_parent->setup_sim_hot_swap_finish (self, res, &error))
mm_obj_dbg (self, "additional SIM hot swap detection setup failed: %s", error->message);
/* The +QUSIM based setup never fails, so we can safely return success here */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
void
mm_shared_quectel_setup_sim_hot_swap (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
Private *priv;
MMPortSerialAt *ports[2];
GTask *task;
GRegex *pattern;
guint i;
g_autoptr(GError) error = NULL;
priv = get_private (MM_SHARED_QUECTEL (self));
task = g_task_new (self, NULL, callback, user_data);
ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
pattern = g_regex_new ("\\+QUSIM:\\s*1\\r\\n", G_REGEX_RAW, 0, NULL);
g_assert (pattern);
for (i = 0; i < G_N_ELEMENTS (ports); i++) {
if (ports[i])
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
pattern,
(MMPortSerialAtUnsolicitedMsgFn)quectel_qusim_unsolicited_handler,
self,
NULL);
}
g_regex_unref (pattern);
mm_obj_dbg (self, "+QUSIM detection set up");
if (!mm_broadband_modem_sim_hot_swap_ports_context_init (MM_BROADBAND_MODEM (self), &error))
mm_obj_warn (self, "failed to initialize SIM hot swap ports context: %s", error->message);
/* Now, if available, setup parent logic */
if (priv->iface_modem_parent->setup_sim_hot_swap &&
priv->iface_modem_parent->setup_sim_hot_swap_finish) {
priv->iface_modem_parent->setup_sim_hot_swap (self,
(GAsyncReadyCallback) parent_setup_sim_hot_swap_ready,
task);
return;
}
/* Otherwise, we're done */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
/*****************************************************************************/
/* SIM hot swap cleanup (Modem interface) */
void
mm_shared_quectel_cleanup_sim_hot_swap (MMIfaceModem *self)
{
mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self));
}
/*****************************************************************************/
/* 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_quectel_location_load_capabilities_finish (MMIfaceModemLocation *self,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gssize value;
value = 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)value;
}
static void
probe_qgps_ready (MMBaseModem *_self,
GAsyncResult *res,
GTask *task)
{
MMSharedQuectel *self;
Private *priv;
MMModemLocationSource sources;
self = MM_SHARED_QUECTEL (g_task_get_source_object (task));
priv = get_private (self);
priv->qgps_supported = (!!mm_base_modem_at_command_finish (_self, res, NULL) ?
FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED);
mm_obj_dbg (self, "GPS management with +QGPS is %ssupported",
priv->qgps_supported ? "" : "not ");
/* Recover parent sources */
sources = GPOINTER_TO_UINT (g_task_get_task_data (task));
/* Only flag as provided those sources not already provided by the parent */
if (priv->qgps_supported == FEATURE_SUPPORTED) {
if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA))
priv->provided_sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW))
priv->provided_sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW;
if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))
priv->provided_sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
sources |= priv->provided_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);
}
/* So we're done, complete */
g_task_return_int (task, sources);
g_object_unref (task);
}
static void
parent_load_capabilities_ready (MMIfaceModemLocation *self,
GAsyncResult *res,
GTask *task)
{
Private *priv;
MMModemLocationSource sources;
GError *error = NULL;
priv = get_private (MM_SHARED_QUECTEL (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;
}
/* Store parent supported sources in task data */
g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL);
/* Probe QGPS support */
g_assert (priv->qgps_supported == FEATURE_SUPPORT_UNKNOWN);
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+QGPS=?",
3,
TRUE, /* cached */
(GAsyncReadyCallback)probe_qgps_ready,
task);
}
void
mm_shared_quectel_location_load_capabilities (MMIfaceModemLocation *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
Private *priv;
task = g_task_new (_self, NULL, callback, user_data);
priv = get_private (MM_SHARED_QUECTEL (_self));
/* Chain up parent's setup */
priv->iface_modem_location_parent->load_capabilities (_self,
(GAsyncReadyCallback)parent_load_capabilities_ready,
task);
}
/*****************************************************************************/
/* Enable location gathering (Location interface) */
/* NOTES:
* 1) "+QGPSCFG=\"nmeasrc\",1" will be necessary for getting location data
* without the nmea port.
* 2) may be necessary to set "+QGPSCFG=\"gpsnmeatype\".
* 3) QGPSXTRA=1 is necessary to support XTRA assistance data for
* faster GNSS location locks.
*/
static const MMBaseModemAtCommand gps_startup[] = {
{ "+QGPSCFG=\"outport\",\"usbnmea\"", 3, FALSE, mm_base_modem_response_processor_no_result_continue },
{ "+QGPS=1", 3, FALSE, mm_base_modem_response_processor_no_result_continue },
{ "+QGPSXTRA=1", 3, FALSE, mm_base_modem_response_processor_no_result_continue },
{ NULL }
};
gboolean
mm_shared_quectel_enable_location_gathering_finish (MMIfaceModemLocation *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
gps_startup_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
MMModemLocationSource source;
GError *error = NULL;
Private *priv;
priv = get_private (MM_SHARED_QUECTEL (self));
mm_base_modem_at_sequence_finish (self, res, NULL, &error);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
source = GPOINTER_TO_UINT (g_task_get_task_data (task));
/* Check if the nmea/raw gps port exists and is available */
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");
} else {
/* GPS port was successfully opened */
priv->enabled_sources |= source;
g_task_return_boolean (task, TRUE);
}
} else {
/* No need to open GPS port */
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_QUECTEL (self));
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_quectel_enable_location_gathering (MMIfaceModemLocation *self,
MMModemLocationSource source,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
Private *priv;
gboolean start_gps = FALSE;
priv = get_private (MM_SHARED_QUECTEL (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);
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
/* Check if the source is provided by the parent */
if (!(priv->provided_sources & source)) {
priv->iface_modem_location_parent->enable_location_gathering (
self,
source,
(GAsyncReadyCallback)parent_enable_location_gathering_ready,
task);
return;
}
/* Only start GPS engine if not done already */
start_gps = ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) &&
!(priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)));
if (start_gps) {
mm_base_modem_at_sequence (
MM_BASE_MODEM (self),
gps_startup,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
(GAsyncReadyCallback)gps_startup_ready,
task);
return;
}
/* If the GPS is already running just return */
priv->enabled_sources |= source;
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
/*****************************************************************************/
/* Disable location gathering (Location interface) */
gboolean
mm_shared_quectel_disable_location_gathering_finish (MMIfaceModemLocation *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
qgps_end_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
disable_location_gathering_parent_ready (MMIfaceModemLocation *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
Private *priv;
priv = get_private (MM_SHARED_QUECTEL (self));
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_quectel_disable_location_gathering (MMIfaceModemLocation *self,
MMModemLocationSource source,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
Private *priv;
priv = get_private (MM_SHARED_QUECTEL (self));
g_assert (priv->iface_modem_location_parent);
task = g_task_new (self, NULL, callback, user_data);
priv->enabled_sources &= ~source;
/* Pass handling to parent if we don't handle it */
if (!(source & priv->provided_sources)) {
/* The step to disable location gathering may not exist */
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)disable_location_gathering_parent_ready,
task);
return;
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
/* Turn off gps on the modem if the source uses gps,
* and there are no other gps sources enabled */
if ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) &&
!(priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) {
/* Close the data port if we don't need it anymore */
if (source & (MM_MODEM_LOCATION_SOURCE_GPS_RAW | MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) {
MMPortSerialGps *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));
}
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+QGPSEND",
3,
FALSE,
(GAsyncReadyCallback)qgps_end_ready,
task);
return;
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
/*****************************************************************************/
/* Check support (Time interface) */
gboolean
mm_shared_quectel_time_check_support_finish (MMIfaceModemTime *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
support_cclk_query_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
/* error never returned */
g_task_return_boolean (task, !!mm_base_modem_at_command_finish (self, res, NULL));
g_object_unref (task);
}
static void
support_cclk_query (GTask *task)
{
MMBaseModem *self;
self = g_task_get_source_object (task);
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CCLK?",
3,
FALSE,
(GAsyncReadyCallback)support_cclk_query_ready,
task);
}
static void
ctzu_set_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
g_autoptr(GError) error = NULL;
if (!mm_base_modem_at_command_finish (self, res, &error))
mm_obj_warn (self, "couldn't enable automatic time zone update: %s", error->message);
support_cclk_query (task);
}
static void
ctzu_test_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
g_autoptr(GError) error = NULL;
const gchar *response;
gboolean supports_disable;
gboolean supports_enable;
gboolean supports_enable_update_rtc;
const gchar *cmd = NULL;
/* If CTZU isn't supported, run CCLK right away */
response = mm_base_modem_at_command_finish (self, res, NULL);
if (!response) {
support_cclk_query (task);
return;
}
if (!mm_quectel_parse_ctzu_test_response (response,
self,
&supports_disable,
&supports_enable,
&supports_enable_update_rtc,
&error)) {
mm_obj_warn (self, "couldn't parse +CTZU test response: %s", error->message);
support_cclk_query (task);
return;
}
/* Custom time support check because some Quectel modems (e.g. EC25) require
* +CTZU=3 in order to have the CCLK? time reported in localtime, instead of
* UTC time. */
if (supports_enable_update_rtc)
cmd = "+CTZU=3";
else if (supports_enable)
cmd = "+CTZU=1";
if (!cmd) {
mm_obj_warn (self, "unknown +CTZU support");
support_cclk_query (task);
return;
}
mm_base_modem_at_command (MM_BASE_MODEM (self),
cmd,
3,
FALSE,
(GAsyncReadyCallback)ctzu_set_ready,
task);
}
void
mm_shared_quectel_time_check_support (MMIfaceModemTime *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),
"+CTZU=?",
3,
TRUE, /* cached! */
(GAsyncReadyCallback)ctzu_test_ready,
task);
}
/*****************************************************************************/
static void
shared_quectel_init (gpointer g_iface)
{
}
GType
mm_shared_quectel_get_type (void)
{
static GType shared_quectel_type = 0;
if (!G_UNLIKELY (shared_quectel_type)) {
static const GTypeInfo info = {
sizeof (MMSharedQuectel), /* class_size */
shared_quectel_init, /* base_init */
NULL, /* base_finalize */
};
shared_quectel_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedQuectel", &info, 0);
g_type_interface_add_prerequisite (shared_quectel_type, MM_TYPE_IFACE_MODEM_FIRMWARE);
}
return shared_quectel_type;
}