aports: the two modemmanager GNSS patches were never committed

git -C <repo> format-patch -o . writes relative to the -C directory, not
the caller's cwd - the files landed in work/ModemManager and the aport
shipped an APKBUILD referencing patches that did not exist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-08-08 21:47:57 +02:00
commit 45caa3ac65
2 changed files with 506 additions and 0 deletions

View file

@ -0,0 +1,255 @@
From 2924dab906b3dc9e3b48e766637618ce53615c23 Mon Sep 17 00:00:00 2001
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
Date: Thu, 2 Jul 2026 15:30:48 +0200
Subject: [PATCH 2/3] shared-qmi: unlock AFW-gated GNSS engines at LOC start
Newer Snapdragon GNSS engines (e.g. gnss8 in the SM7635/Fairphone 6)
only allow positioning for clients registered as application framework
("AFW", i.e. the Android framework) clients: for anyone else,
QMI_LOC_REG_EVENTS fails with INVALID_ARGUMENT and QMI_LOC_START with
GENERAL_FAILURE ("IsPosRequestAllowed: FORCED_DISABLED" in the
engine's own logic). ModemManager issued START first and never
identified itself, so GPS could not be enabled at all on these devices.
Reorder the LOC bring-up to register-events -> start (the order the
Qualcomm location HAL uses), identifying as an AFW client with the
client identification TLVs added in libqmi 1.39.1, and bump the libqmi
requirement accordingly.
Isolating the TLVs on the gated engine showed the client type is the
only TLV actually checked (the client string and Enable Positioning
Request Notification are ignored for AFW clients, matching their
documentation in location_service_v02.h), so only the client type and
an informational "MM" client string are sent. Should an engine reject
the registration carrying the identification TLVs, it is retried once
without them, i.e. exactly what was always sent before.
This changes the LOC messages every QMI modem sees, and it has only
been tested on the Fairphone 6: the effect of the new TLVs and of the
reordering on other LOC engines is unknown (the retry above bounds the
worst case for the TLVs, not for the reordering). Regression testing
on other QMI modems would be very welcome.
The tracking-events registration that previously followed a successful
START is thereby folded into the initial register-events; the LOC
client and NMEA handler are still only recorded in the private info
once START has succeeded. This also fixes a pre-existing issue where a
register-events failure after a successful START returned an error
while leaving the engine running with no indication handlers connected.
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
---
meson.build | 2 +-
src/mm-shared-qmi.c | 139 +++++++++++++++++++++++++++-----------------
2 files changed, 86 insertions(+), 55 deletions(-)
diff --git a/meson.build b/meson.build
index 3cf29e80..e5e3d0be 100644
--- a/meson.build
+++ b/meson.build
@@ -277,7 +277,7 @@ config_h.set('WITH_MBIM', enable_mbim)
# QMI support (enabled by default)
enable_qmi = get_option('qmi')
if enable_qmi
- qmi_glib_dep = dependency('qmi-glib', version: '>= 1.37.95',
+ qmi_glib_dep = dependency('qmi-glib', version: '>= 1.39.1',
required: not get_option('subproject-fallback'))
if not qmi_glib_dep.found() and get_option('subproject-fallback')
diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c
index d93698a6..09aa987a 100644
--- a/src/mm-shared-qmi.c
+++ b/src/mm-shared-qmi.c
@@ -5646,33 +5646,67 @@ pds_gps_service_state_start_ready (QmiClientPds *client,
qmi_message_pds_set_auto_tracking_state_input_unref (input);
}
+static void loc_register_events_ready (QmiClientLoc *client, GAsyncResult *res, GTask *task);
+
+/* Send the LOC event registration, optionally identifying as an
+ * application framework ("AFW", i.e. the Android framework) client: some
+ * GNSS engines (e.g. gnss8 in the SM7635) only allow positioning for AFW
+ * clients, and reject both event registration and QMI_LOC_START for
+ * unidentified clients. */
static void
-loc_register_events_ready (QmiClientLoc *client,
- GAsyncResult *res,
- GTask *task)
+gps_engine_loc_register_events (QmiClientLoc *client,
+ GTask *task,
+ gboolean identify)
{
- MMSharedQmi *self;
- Private *priv;
- QmiMessageLocRegisterEventsOutput *output;
- GError *error = NULL;
+ QmiMessageLocRegisterEventsInput *input;
+ guint64 event_mask;
+
+ event_mask = QMI_LOC_EVENT_REGISTRATION_FLAG_NMEA |
+ 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);
+ if (identify) {
+ qmi_message_loc_register_events_input_set_client_type (input, QMI_LOC_CLIENT_TYPE_AFW, NULL);
+ qmi_message_loc_register_events_input_set_client_string_id (input, "MM", NULL);
+ }
+ g_task_set_task_data (task, GUINT_TO_POINTER (identify), NULL);
+ qmi_client_loc_register_events (client,
+ input,
+ 10,
+ NULL,
+ (GAsyncReadyCallback) loc_register_events_ready,
+ task);
+ qmi_message_loc_register_events_input_unref (input);
+}
+
+static void
+loc_start_ready (QmiClientLoc *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMSharedQmi *self;
+ Private *priv;
+ QmiMessageLocStartOutput *output;
+ GError *error = NULL;
- output = qmi_client_loc_register_events_finish (client, res, &error);
- if (!output) {
+ output = qmi_client_loc_start_finish (client, res, &error);
+ if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
- }
+ }
- if (!qmi_message_loc_register_events_output_get_result (output, &error)) {
- g_prefix_error (&error, "Couldn't not register tracking events: ");
+ if (!qmi_message_loc_start_output_get_result (output, &error)) {
+ g_prefix_error (&error, "Couldn't start GPS engine: ");
g_task_return_error (task, error);
g_object_unref (task);
- qmi_message_loc_register_events_output_unref (output);
+ qmi_message_loc_start_output_unref (output);
return;
}
- qmi_message_loc_register_events_output_unref (output);
+ qmi_message_loc_start_output_unref (output);
self = g_task_get_source_object (task);
priv = get_private (self);
@@ -5691,16 +5725,16 @@ loc_register_events_ready (QmiClientLoc *client,
}
static void
-loc_start_ready (QmiClientLoc *client,
- GAsyncResult *res,
- GTask *task)
+loc_register_events_ready (QmiClientLoc *client,
+ GAsyncResult *res,
+ GTask *task)
{
- QmiMessageLocRegisterEventsInput *input;
- QmiMessageLocStartOutput *output;
- guint64 event_mask;
- GError *error = NULL;
+ MMSharedQmi *self;
+ QmiMessageLocRegisterEventsOutput *output;
+ QmiMessageLocStartInput *input;
+ GError *error = NULL;
- output = qmi_client_loc_start_finish (client, res, &error);
+ output = qmi_client_loc_register_events_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
@@ -5708,32 +5742,40 @@ loc_start_ready (QmiClientLoc *client,
return;
}
- if (!qmi_message_loc_start_output_get_result (output, &error)) {
- g_prefix_error (&error, "Couldn't start GPS engine: ");
+ if (!qmi_message_loc_register_events_output_get_result (output, &error)) {
+ qmi_message_loc_register_events_output_unref (output);
+ /* Engines predating the client identification TLVs may reject the
+ * identified registration; retry once without identifying, which
+ * matches what was always sent before */
+ if (g_task_get_task_data (task)) {
+ self = g_task_get_source_object (task);
+ mm_obj_dbg (self, "couldn't register location events as AFW client: %s; retrying without client identification", error->message);
+ g_clear_error (&error);
+ gps_engine_loc_register_events (client, task, FALSE);
+ return;
+ }
+ g_prefix_error (&error, "Couldn't register location events: ");
g_task_return_error (task, error);
g_object_unref (task);
- qmi_message_loc_start_output_unref (output);
return;
}
- qmi_message_loc_start_output_unref (output);
+ qmi_message_loc_register_events_output_unref (output);
- 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_POSITION_REQUEST;
- qmi_message_loc_register_events_input_set_event_registration_mask (
- input, event_mask, NULL);
- qmi_client_loc_register_events (client,
- input,
- 10,
- NULL,
- (GAsyncReadyCallback) loc_register_events_ready,
- task);
- qmi_message_loc_register_events_input_unref (input);
+ input = qmi_message_loc_start_input_new ();
+ qmi_message_loc_start_input_set_session_id (input, DEFAULT_LOC_SESSION_ID, NULL);
+ qmi_message_loc_start_input_set_intermediate_report_state (input, QMI_LOC_INTERMEDIATE_REPORT_STATE_DISABLE, NULL);
+ qmi_message_loc_start_input_set_minimum_interval_between_position_reports (input, 1000, NULL);
+ qmi_message_loc_start_input_set_fix_recurrence_type (input, QMI_LOC_FIX_RECURRENCE_TYPE_REQUEST_PERIODIC_FIXES, NULL);
+ qmi_client_loc_start (client,
+ input,
+ 10,
+ NULL,
+ (GAsyncReadyCallback) loc_start_ready,
+ task);
+ qmi_message_loc_start_input_unref (input);
}
-
static void
start_gps_engine (MMSharedQmi *self,
GAsyncReadyCallback callback,
@@ -5771,20 +5813,9 @@ start_gps_engine (MMSharedQmi *self,
MM_PORT_QMI_FLAG_DEFAULT,
NULL);
if (client) {
- QmiMessageLocStartInput *input;
-
- input = qmi_message_loc_start_input_new ();
- qmi_message_loc_start_input_set_session_id (input, DEFAULT_LOC_SESSION_ID, NULL);
- qmi_message_loc_start_input_set_intermediate_report_state (input, QMI_LOC_INTERMEDIATE_REPORT_STATE_DISABLE, NULL);
- qmi_message_loc_start_input_set_minimum_interval_between_position_reports (input, 1000, NULL);
- qmi_message_loc_start_input_set_fix_recurrence_type (input, QMI_LOC_FIX_RECURRENCE_TYPE_REQUEST_PERIODIC_FIXES, NULL);
- qmi_client_loc_start (QMI_CLIENT_LOC (client),
- input,
- 10,
- NULL,
- (GAsyncReadyCallback) loc_start_ready,
- task);
- qmi_message_loc_start_input_unref (input);
+ /* Register events before starting; register-before-start is the
+ * order the Qualcomm location HAL uses */
+ gps_engine_loc_register_events (QMI_CLIENT_LOC (client), task, TRUE);
return;
}
--
2.55.0

View file

@ -0,0 +1,251 @@
From 7566587e77d299ab0f67613ae03cab9973e351db Mon Sep 17 00:00:00 2001
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
Date: Thu, 2 Jul 2026 15:44:16 +0200
Subject: [PATCH 3/3] shared-qmi: derive location from Position Report
indications
Some GNSS engines (e.g. gnss8 in the SM7635/Fairphone 6) report fixes
only via QMI_LOC Position Report indications and never emit NMEA (Set
NMEA Types returns NotSupported), while location is derived exclusively
from NMEA traces. As a result no location was surfaced even with the
engine running and tracking satellites.
Register for the POSITION_REPORT event and synthesize minimal $GPGGA
and $GPRMC sentences from Position Report indications carrying a
successful fix (intermediate reports are disabled at engine start),
feeding the existing NMEA-based pipeline so that gps-raw and gps-nmea
both work on these engines too. Synthesizing NMEA rather than extending
the location interface keeps the change local to the QMI backend.
To avoid polluting the location output of engines that do emit NMEA
with duplicate synthesized sentences, position reports are ignored as
soon as a real NMEA indication has been seen for the current engine
run. In the worst case an NMEA-emitting engine could have a few
synthesized sentences slip in before its first real NMEA arrives, but
NMEA engines emit sentences immediately (fix or not) while a position
report needs a valid fix, so real NMEA wins that race in practice.
Values the position report does not carry are left as empty NMEA
fields rather than invented, and $GPRMC is only synthesized when the
report carries a UTC timestamp, as an RMC sentence without one is
meaningless.
Tested on the Fairphone 6 (SM7635): with both patches applied,
enabling gps-nmea/gps-raw via mmcli surfaces a correct live fix
(lat/lon/altitude/UTC) from the synthesized sentences; position
reports without a valid fix (session status 'timeout' while indoors)
are correctly ignored. The effect on other LOC engines (now also
receiving the POSITION_REPORT registration) has not been verified on
real hardware.
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
---
src/mm-shared-qmi.c | 147 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 147 insertions(+)
diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c
index 09aa987a..cad75080 100644
--- a/src/mm-shared-qmi.c
+++ b/src/mm-shared-qmi.c
@@ -88,6 +88,8 @@ typedef struct {
gulong pds_location_event_report_indication_id;
QmiClient *loc_client;
gulong loc_location_nmea_indication_id;
+ gulong loc_location_position_report_indication_id;
+ gboolean loc_location_nmea_received;
gchar **loc_assistance_data_servers;
guint32 loc_assistance_data_max_file_size;
guint32 loc_assistance_data_max_part_size;
@@ -126,6 +128,8 @@ private_free (Private *priv)
g_object_unref (priv->pds_client);
if (priv->loc_location_nmea_indication_id)
g_signal_handler_disconnect (priv->loc_client, priv->loc_location_nmea_indication_id);
+ if (priv->loc_location_position_report_indication_id)
+ g_signal_handler_disconnect (priv->loc_client, priv->loc_location_position_report_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)
@@ -5161,6 +5165,10 @@ loc_stop_ready (QmiClientLoc *client,
g_signal_handler_disconnect (priv->loc_client, priv->loc_location_nmea_indication_id);
priv->loc_location_nmea_indication_id = 0;
}
+ if (priv->loc_location_position_report_indication_id != 0) {
+ g_signal_handler_disconnect (priv->loc_client, priv->loc_location_position_report_indication_id);
+ priv->loc_location_position_report_indication_id = 0;
+ }
g_clear_object (&priv->loc_client);
}
@@ -5264,11 +5272,142 @@ loc_location_nmea_indication_cb (QmiClientLoc *client,
if (!nmea)
return;
+ priv->loc_location_nmea_received = TRUE;
+
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);
}
+/* Build a NMEA sentence ("$" + body + "*" + XOR checksum) from its body */
+static gchar *
+nmea_sentence_new (const gchar *body)
+{
+ guint8 checksum = 0;
+ const gchar *p;
+
+ for (p = body; *p; p++)
+ checksum ^= (guint8) *p;
+ return g_strdup_printf ("$%s*%02X", body, checksum);
+}
+
+/* Some GNSS engines (e.g. gnss8 in the SM7635) report fixes only via
+ * Position Report indications and never emit NMEA. Synthesize minimal
+ * $GPGGA/$GPRMC sentences from the position report so that the existing
+ * NMEA-based location pipeline (gps-raw and gps-nmea) also works on those
+ * engines. Not used on engines seen emitting NMEA themselves. */
+static void
+loc_location_position_report_indication_cb (QmiClientLoc *client,
+ QmiIndicationLocPositionReportOutput *output,
+ MMSharedQmi *self)
+{
+ Private *priv;
+ QmiLocSessionStatus session_status = QMI_LOC_SESSION_STATUS_GENERAL_FAILURE;
+ gdouble latitude = 0.0;
+ gdouble longitude = 0.0;
+ gfloat altitude = 0.0;
+ gfloat speed = 0.0;
+ gfloat heading = 0.0;
+ guint64 timestamp = 0;
+ gdouble absolute;
+ gdouble minutes;
+ guint degrees;
+ gchar ns;
+ gchar ew;
+ gchar latbuf[32];
+ gchar lonbuf[32];
+ gchar timebuf[16] = "";
+ gchar datebuf[8] = "";
+ gchar altbuf[16] = "";
+ gchar speedbuf[16] = "";
+ gchar headingbuf[16] = "";
+ g_autofree gchar *gga_body = NULL;
+ g_autofree gchar *gga = NULL;
+
+ priv = get_private (self);
+
+ /* Engines that emit NMEA themselves don't need synthesized sentences */
+ if (priv->loc_location_nmea_received)
+ return;
+
+ /* Only surface reports carrying a valid fix; intermediate reports are
+ * disabled at engine start */
+ if (!qmi_indication_loc_position_report_output_get_session_status (output, &session_status, NULL) ||
+ (session_status != QMI_LOC_SESSION_STATUS_SUCCESS))
+ return;
+
+ if (!qmi_indication_loc_position_report_output_get_latitude (output, &latitude, NULL) ||
+ !qmi_indication_loc_position_report_output_get_longitude (output, &longitude, NULL))
+ return;
+
+ /* Unavailable optional values are left as empty NMEA fields */
+ if (qmi_indication_loc_position_report_output_get_utc_timestamp (output, &timestamp, NULL)) {
+ g_autoptr(GDateTime) utc = NULL;
+
+ utc = g_date_time_new_from_unix_utc ((gint64) (timestamp / 1000));
+ if (utc) {
+ g_snprintf (timebuf, sizeof (timebuf), "%02d%02d%02d.%02u",
+ g_date_time_get_hour (utc),
+ g_date_time_get_minute (utc),
+ g_date_time_get_second (utc),
+ (guint) ((timestamp % 1000) / 10));
+ g_snprintf (datebuf, sizeof (datebuf), "%02d%02d%02d",
+ g_date_time_get_day_of_month (utc),
+ g_date_time_get_month (utc),
+ g_date_time_get_year (utc) % 100);
+ }
+ }
+ if (qmi_indication_loc_position_report_output_get_altitude_from_sealevel (output, &altitude, NULL))
+ g_snprintf (altbuf, sizeof (altbuf), "%.1f", (gdouble) altitude);
+ if (qmi_indication_loc_position_report_output_get_horizontal_speed (output, &speed, NULL))
+ g_snprintf (speedbuf, sizeof (speedbuf), "%.1f", (gdouble) speed * 1.943844); /* m/s -> knots */
+ if (qmi_indication_loc_position_report_output_get_heading (output, &heading, NULL))
+ g_snprintf (headingbuf, sizeof (headingbuf), "%.1f", (gdouble) heading);
+
+ ns = (latitude >= 0.0) ? 'N' : 'S';
+ absolute = (latitude >= 0.0) ? latitude : -latitude;
+ degrees = (guint) absolute;
+ minutes = (absolute - degrees) * 60.0;
+ /* %.6f rounding must not print out-of-spec 60.000000 minutes */
+ if (minutes >= 60.0 - 0.0000005) {
+ minutes = 0.0;
+ degrees++;
+ }
+ g_snprintf (latbuf, sizeof (latbuf), "%02u%09.6f", degrees, minutes);
+
+ ew = (longitude >= 0.0) ? 'E' : 'W';
+ absolute = (longitude >= 0.0) ? longitude : -longitude;
+ degrees = (guint) absolute;
+ minutes = (absolute - degrees) * 60.0;
+ if (minutes >= 60.0 - 0.0000005) {
+ minutes = 0.0;
+ degrees++;
+ }
+ g_snprintf (lonbuf, sizeof (lonbuf), "%03u%09.6f", degrees, minutes);
+
+ gga_body = g_strdup_printf ("GPGGA,%s,%s,%c,%s,%c,1,,,%s,M,,M,,",
+ timebuf, latbuf, ns, lonbuf, ew, altbuf);
+ gga = nmea_sentence_new (gga_body);
+
+ mm_obj_dbg (self, "[position-report] %s", gga);
+ mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), gga);
+ mm_location_cache_update_from_nmea (priv->location_cache, gga);
+
+ /* An RMC sentence without a UTC timestamp would be meaningless */
+ if (timebuf[0]) {
+ g_autofree gchar *rmc_body = NULL;
+ g_autofree gchar *rmc = NULL;
+
+ rmc_body = g_strdup_printf ("GPRMC,%s,A,%s,%c,%s,%c,%s,%s,%s,,",
+ timebuf, latbuf, ns, lonbuf, ew, speedbuf, headingbuf, datebuf);
+ rmc = nmea_sentence_new (rmc_body);
+
+ mm_obj_dbg (self, "[position-report] %s", rmc);
+ mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), rmc);
+ mm_location_cache_update_from_nmea (priv->location_cache, rmc);
+ }
+}
+
/*****************************************************************************/
/* Location: internal helper: setup minimum required NMEA traces */
@@ -5662,6 +5801,7 @@ gps_engine_loc_register_events (QmiClientLoc *client,
guint64 event_mask;
event_mask = QMI_LOC_EVENT_REGISTRATION_FLAG_NMEA |
+ QMI_LOC_EVENT_REGISTRATION_FLAG_POSITION_REPORT |
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_TIME_REQUEST |
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_POSITION_REQUEST;
input = qmi_message_loc_register_events_input_new ();
@@ -5713,12 +5853,19 @@ loc_start_ready (QmiClientLoc *client,
g_assert (!priv->loc_client);
g_assert (!priv->loc_location_nmea_indication_id);
+ g_assert (!priv->loc_location_position_report_indication_id);
priv->loc_client = QMI_CLIENT (g_object_ref (client));
+ priv->loc_location_nmea_received = FALSE;
priv->loc_location_nmea_indication_id =
g_signal_connect (client,
"nmea",
G_CALLBACK (loc_location_nmea_indication_cb),
self);
+ priv->loc_location_position_report_indication_id =
+ g_signal_connect (client,
+ "position-report",
+ G_CALLBACK (loc_location_position_report_indication_cb),
+ self);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
--
2.55.0