Files
ModemManager/plugins/quectel/mm-shared-quectel.c
Dylan Van Assche b70fd64417 quectel: add name to device IDs
fwupd expects device IDs which are unique for each device and its
variants. However, Quectel re-uses the same USB VID & PID among
different variants such as EG25, EC25, EC20, etc. Moreover, each
variant may have subvariants such as EG25GGB, EG25GGC, EG25AFF,
EG25AFX, etc.

Add the name of the modem to the device IDs to build more unique device
IDs such as USB\VID_2C7C&PID_0125&REV_0001&NAME_EC25GGB.
2022-02-09 11:07:49 +01:00

994 lines
35 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;
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");
/* 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);
}
/*****************************************************************************/
/* 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;
}