
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.
1007 lines
36 KiB
C
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;
|
|
}
|