shared-qmi: handle inject position requests from the modem

To be able to have quick position fix GNSS engine needs to know
 - predicted orbits data,
 - current time,
 - approximate position.
Modem sends indications with requests, when it needs corresponding
information. Predicted orbits can be injected using
'mmcli -m any --location-inject-assistance-data=<file>'. But to
accelerate time to first fix after cold start extra information is
needed like time and coarse position.

Inject position requests are received, when GPS session is initiated
using QMI_LOC_START. In inject position request modem sends
information about its last known position. If last known position is
zero, when this is a cold start and inject position is needed to
improve TTFF.
Last position from the modem is cached in ModemManager and is injected
next time, when modem requests it. For example, if modem is restarted.

When timestamp on injected position is too old, than position is
ignored and location engine does cold start. Therefore current time is
used for injected position.

When injected position is far away the actual one (for example, after
flight), after some time location engine falls back to cold start.

Tested on Quectel EG25-G.

Signed-off-by: Andrey Skvortsov <andrej.skvortzov@gmail.com>
This commit is contained in:
Andrey Skvortsov
2026-01-10 02:04:10 +03:00
parent a108a0fe7c
commit 8b6116fe6c
+196 -5
View File
@@ -30,6 +30,7 @@
#include "mm-iface-modem.h"
#include "mm-iface-modem-3gpp.h"
#include "mm-iface-modem-location.h"
#include "mm-location-cache.h"
#include "mm-sim-qmi.h"
#include "mm-shared-qmi.h"
#include "mm-modem-helpers-qmi.h"
@@ -92,6 +93,7 @@ typedef struct {
guint32 loc_assistance_data_max_part_size;
gulong loc_assistance_inject_time_req_indication_id;
gulong loc_assistance_inject_position_req_indication_id;
/* Carrier config helpers */
gboolean config_active_default;
@@ -104,6 +106,9 @@ typedef struct {
gulong uim_slot_status_indication_id;
gulong uim_refresh_indication_id;
guint uim_refresh_start_timeout_id;
/* Cached last location from nmea */
MMLocationCache *location_cache;
} Private;
static void
@@ -123,6 +128,8 @@ private_free (Private *priv)
g_signal_handler_disconnect (priv->loc_client, priv->loc_location_nmea_indication_id);
if (priv->loc_assistance_inject_time_req_indication_id)
g_signal_handler_disconnect (priv->loc_client, priv->loc_assistance_inject_time_req_indication_id);
if (priv->loc_assistance_inject_position_req_indication_id)
g_signal_handler_disconnect (priv->loc_client, priv->loc_assistance_inject_position_req_indication_id);
if (priv->loc_client)
g_object_unref (priv->loc_client);
if (priv->uim_slot_status_indication_id)
@@ -135,6 +142,7 @@ private_free (Private *priv)
g_object_unref (priv->uim_client);
if (priv->uim_refresh_start_timeout_id)
g_source_remove (priv->uim_refresh_start_timeout_id);
g_clear_object (&priv->location_cache);
if (priv->feature_nas_ssp_acquisition_order_preference_array)
g_array_unref (priv->feature_nas_ssp_acquisition_order_preference_array);
g_strfreev (priv->loc_assistance_data_servers);
@@ -144,7 +152,8 @@ private_free (Private *priv)
static Private *
get_private (MMSharedQmi *self)
{
Private *priv;
g_autoptr(GError) error = NULL;
Private *priv;
if (G_UNLIKELY (!private_quark))
private_quark = g_quark_from_static_string (PRIVATE_TAG);
@@ -159,6 +168,10 @@ get_private (MMSharedQmi *self)
priv->feature_nas_ssp_acquisition_order_preference = FEATURE_UNKNOWN;
priv->config_active_i = -1;
priv->location_cache = mm_location_cache_new ();
if (!mm_location_cache_load (priv->location_cache, &error))
mm_obj_warn (priv->location_cache, "%s", error->message);
/* Setup parent class' MMIfaceModemLocation */
g_assert (MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface);
priv->iface_modem_location_parent = MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface (self);
@@ -5160,13 +5173,17 @@ stop_gps_engine (MMSharedQmi *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
Private *priv;
GTask *task;
Private *priv;
g_autoptr(GError) error = NULL;
priv = get_private (self);
task = g_task_new (self, NULL, callback, user_data);
if (!mm_location_cache_save (priv->location_cache, &error))
mm_obj_warn (self, "%s", error->message);
if (priv->pds_client) {
QmiMessagePdsSetGpsServiceStateInput *input;
@@ -5213,7 +5230,9 @@ pds_location_event_report_indication_cb (QmiClientPds *clie
{
QmiPdsPositionSessionStatus session_status;
const gchar *nmea;
Private *priv;
priv = get_private (MM_SHARED_QMI (self));
if (qmi_indication_pds_event_report_output_get_position_session_status (
output,
&session_status,
@@ -5228,6 +5247,7 @@ pds_location_event_report_indication_cb (QmiClientPds *clie
NULL)) {
mm_obj_dbg (self, "[NMEA] %s", nmea);
mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), nmea);
mm_location_cache_update_from_nmea (priv->location_cache, nmea);
}
}
@@ -5237,13 +5257,16 @@ loc_location_nmea_indication_cb (QmiClientLoc *client,
MMSharedQmi *self)
{
const gchar *nmea = NULL;
Private *priv;
priv = get_private (MM_SHARED_QMI (self));
qmi_indication_loc_nmea_output_get_nmea_string (output, &nmea, NULL);
if (!nmea)
return;
mm_obj_dbg (self, "[NMEA] %s", nmea);
mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), nmea);
mm_location_cache_update_from_nmea (priv->location_cache, nmea);
}
/*****************************************************************************/
@@ -5697,7 +5720,8 @@ loc_start_ready (QmiClientLoc *client,
input = qmi_message_loc_register_events_input_new ();
event_mask = QMI_LOC_EVENT_REGISTRATION_FLAG_NMEA |
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_TIME_REQUEST;
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_TIME_REQUEST |
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_POSITION_REQUEST;
qmi_message_loc_register_events_input_set_event_registration_mask (
input, event_mask, NULL);
qmi_client_loc_register_events (client,
@@ -6852,6 +6876,164 @@ loc_location_inject_time_req_indication_cb (QmiClientLoc
task);
}
/*****************************************************************************/
/* Location: Inject position */
typedef struct {
guint timeout_id;
gulong indication_id;
QmiClientLoc *client;
} InjectPositionContext;
static void
inject_position_context_cleanup_action (InjectPositionContext *ctx)
{
if (ctx->timeout_id) {
g_source_remove (ctx->timeout_id);
ctx->timeout_id = 0;
}
if (ctx->indication_id) {
if (ctx->client)
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
ctx->indication_id = 0;
}
}
static void
inject_position_context_free (InjectPositionContext *ctx)
{
inject_position_context_cleanup_action (ctx);
g_clear_object (&ctx->client);
g_slice_free (InjectPositionContext, ctx);
}
static void
inject_position_finish_task (GTask *_task,
GError *error)
{
InjectPositionContext *ctx;
g_autoptr(GTask) task = _task;
ctx = g_task_get_task_data (task);
inject_position_context_cleanup_action (ctx);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
}
static gboolean
loc_location_inject_position_indication_timed_out (GTask *task)
{
inject_position_finish_task (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
"Operation timed out"));
return G_SOURCE_REMOVE;
}
static void
loc_location_inject_position_indication_cb (QmiClientLoc *client,
QmiIndicationLocInjectPositionOutput *output,
GTask *task)
{
QmiLocIndicationStatus status;
GError *error = NULL;
if (!qmi_indication_loc_inject_position_output_get_indication_status (output, &status, &error))
g_prefix_error (&error, "QMI operation failed: ");
else
mm_error_from_qmi_loc_indication_status (status, &error);
inject_position_finish_task (task, error);
}
static void
loc_location_inject_position_ready (QmiClientLoc *client,
GAsyncResult *res,
GTask *task)
{
g_autoptr(QmiMessageLocInjectPositionOutput) output = NULL;
GError *error = NULL;
output = qmi_client_loc_inject_position_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
inject_position_finish_task (task, error);
return;
}
if (!qmi_message_loc_inject_position_output_get_result (output, &error)) {
inject_position_finish_task (task, error);
return;
}
}
static void
loc_location_inject_position_req_indication_cb (QmiClientLoc *client,
QmiIndicationLocInjectPositionRequestOutput *output,
MMSharedQmi *self)
{
g_autoptr(QmiMessageLocInjectPositionInput) input = NULL;
InjectPositionContext *ctx;
Private *priv;
GTask *task;
gdouble lat;
gdouble lon;
guint64 utc_ms = 0;
gdouble last_lat;
gdouble last_lon;
guint64 timestamp;
qmi_indication_loc_inject_position_request_output_get_latitude (output, &lat, NULL);
qmi_indication_loc_inject_position_request_output_get_longitude (output, &lon, NULL);
qmi_indication_loc_inject_position_request_output_get_utc_timestamp (output, &timestamp, NULL);
/* check whether modem already has position */
if ((lat != 0.0) && (lon != 0.0) && (timestamp != 0))
return;
priv = get_private (MM_SHARED_QMI (self));
mm_location_cache_get_lat_lon (priv->location_cache, &last_lat, &last_lon);
if ((last_lat == MM_LOCATION_LATITUDE_UNKNOWN) ||
(last_lon == MM_LOCATION_LONGITUDE_UNKNOWN))
return;
task = g_task_new (self, NULL, NULL, NULL);
ctx = g_slice_new0 (InjectPositionContext);
g_task_set_task_data (task, ctx, (GDestroyNotify)inject_position_context_free);
ctx->client = g_object_ref (client);
input = qmi_message_loc_inject_position_input_new ();
qmi_message_loc_inject_position_input_set_latitude (input, last_lat, NULL);
qmi_message_loc_inject_position_input_set_longitude (input, last_lon, NULL);
qmi_message_loc_inject_position_input_set_position_source (input, QMI_LOC_POSITION_SOURCE_OTHER, NULL);
// it's not recommended to advertise accuracy better than 1000 meters to the modem
qmi_message_loc_inject_position_input_set_horizontal_uncertainty_circular (input, 1000, NULL);
qmi_message_loc_inject_position_input_set_horizontal_confidence (input, 68, NULL);
utc_ms = g_get_real_time () / 1000;
qmi_message_loc_inject_position_input_set_utc_timestamp (input, utc_ms, NULL);
ctx->timeout_id = g_timeout_add_seconds (10,
(GSourceFunc) loc_location_inject_position_indication_timed_out,
task);
ctx->indication_id = g_signal_connect (client,
"inject-position",
G_CALLBACK (loc_location_inject_position_indication_cb),
task);
qmi_client_loc_inject_position (
QMI_CLIENT_LOC (ctx->client),
input,
10,
NULL, /* cancellable */
(GAsyncReadyCallback)loc_location_inject_position_ready,
task);
}
static void
loc_register_event_inject_req_ready (QmiClientLoc *client,
GAsyncResult *res,
@@ -6882,6 +7064,14 @@ loc_register_event_inject_req_ready (QmiClientLoc *client,
"inject-time-request",
G_CALLBACK (loc_location_inject_time_req_indication_cb),
self);
g_assert (!priv->loc_assistance_inject_position_req_indication_id);
priv->loc_assistance_inject_position_req_indication_id =
g_signal_connect (client,
"inject-position-request",
G_CALLBACK (loc_location_inject_position_req_indication_cb),
self);
out:
g_task_return_int (task, MM_MODEM_LOCATION_ASSISTANCE_DATA_TYPE_XTRA);
g_object_unref (task);
@@ -6955,7 +7145,8 @@ out:
g_autoptr(QmiMessageLocRegisterEventsInput) input = NULL;
guint64 event_mask;
event_mask = QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_TIME_REQUEST;
event_mask = QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_TIME_REQUEST |
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_POSITION_REQUEST;
input = qmi_message_loc_register_events_input_new ();
qmi_message_loc_register_events_input_set_event_registration_mask (
input, event_mask, NULL);