From 6938ec10b26e5183ac2764f8c0f196c6552306de Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Wed, 14 Oct 2026 18:42:47 +0200 Subject: [PATCH] modemmanager r102: quiet the bearer's WDS event reports in terse With --test-quick-suspend-resume (fp6-device-tweaks 6) ModemManager sets the modem terse before a suspend, but every connected QMI bearer keeps its WDS event report subscription, and on the FP6 the data bearer technology flips every few seconds while idle (LTE with 5G NSA), so the report still ended a deep cycle within seconds. The patch adds a bearer step to terse: the QMI bearer drops the event report of its WDS clients and the resume sync re-arms them. Dev phone, six logind-driven deep cycles: the report is gone from the wake trace; three cycles ran their full 60 s alarm, the other three were ended only by the modem writing its EFS through rmtfs. Same upstream commit, patch 0004 on top of the three GNSS patches. --- ...-WDS-event-reports-in-terse-re-arm-t.patch | 342 ++++++++++++++++++ aports/temp/modemmanager/APKBUILD | 4 +- 2 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 aports/temp/modemmanager/0004-bearer-quiet-the-WDS-event-reports-in-terse-re-arm-t.patch diff --git a/aports/temp/modemmanager/0004-bearer-quiet-the-WDS-event-reports-in-terse-re-arm-t.patch b/aports/temp/modemmanager/0004-bearer-quiet-the-WDS-event-reports-in-terse-re-arm-t.patch new file mode 100644 index 0000000..a703d03 --- /dev/null +++ b/aports/temp/modemmanager/0004-bearer-quiet-the-WDS-event-reports-in-terse-re-arm-t.patch @@ -0,0 +1,342 @@ +From 1a30e96fb992bfbd50574aa3b0467f48ce1dfd9d Mon Sep 17 00:00:00 2001 +From: Jorijn van der Graaf +Date: Wed, 14 Oct 2026 18:18:45 +0200 +Subject: [PATCH] bearer: quiet the WDS event reports in terse, re-arm them on + resume + +The terse operation that runs before a suspend stops the 3GPP registration +and signal indications, but every connected QMI bearer keeps its WDS event +report subscription (data bearer technology, data systems, ...). Each of +those reports wakes a suspended host: on a Fairphone 6 on LTE with 5G NSA +the bearer technology changes every few seconds while the host is idle, +and the report ended a deep suspend cycle within seconds. + +Add a terse step for the bearers: MMBaseBearer gets an optional terse() +vfunc, the QMI bearer implements it by dropping the event report of its +IPv4/IPv6 WDS clients, and the quick-resume synchronization (the bearer's +reload_connection_status) re-arms them. +--- + src/mm-base-bearer.c | 50 +++++++++++++++++++ + src/mm-base-bearer.h | 18 +++++++ + src/mm-bearer-qmi.c | 101 +++++++++++++++++++++++++++++++++++++++ + src/mm-broadband-modem.c | 49 +++++++++++++++++++ + 4 files changed, 218 insertions(+) + +diff --git a/src/mm-base-bearer.c b/src/mm-base-bearer.c +index f9b86884..e89843e5 100644 +--- a/src/mm-base-bearer.c ++++ b/src/mm-base-bearer.c +@@ -1713,6 +1713,56 @@ mm_base_bearer_sync (MMBaseBearer *self, + + #endif + ++/*****************************************************************************/ ++/* Terse (quiet the modem's unsolicited reports before a suspend) */ ++ ++#if defined WITH_SUSPEND_RESUME ++ ++gboolean ++mm_base_bearer_terse_finish (MMBaseBearer *self, ++ GAsyncResult *res, ++ GError **error) ++{ ++ return g_task_propagate_boolean (G_TASK (res), error); ++} ++ ++static void ++terse_ready (MMBaseBearer *self, ++ GAsyncResult *res, ++ GTask *task) ++{ ++ GError *error = NULL; ++ ++ if (!MM_BASE_BEARER_GET_CLASS (self)->terse_finish (self, res, &error)) ++ g_task_return_error (task, error); ++ else ++ g_task_return_boolean (task, TRUE); ++ g_object_unref (task); ++} ++ ++void ++mm_base_bearer_terse (MMBaseBearer *self, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ GTask *task; ++ ++ task = g_task_new (self, NULL, callback, user_data); ++ ++ /* Nothing to quiet unless the bearer is connected and its class knows how */ ++ if (self->priv->status != MM_BEARER_STATUS_CONNECTED || ++ !MM_BASE_BEARER_GET_CLASS (self)->terse || ++ !MM_BASE_BEARER_GET_CLASS (self)->terse_finish) { ++ g_task_return_boolean (task, TRUE); ++ g_object_unref (task); ++ return; ++ } ++ ++ MM_BASE_BEARER_GET_CLASS (self)->terse (self, (GAsyncReadyCallback)terse_ready, task); ++} ++ ++#endif ++ + /*****************************************************************************/ + + static gchar * +diff --git a/src/mm-base-bearer.h b/src/mm-base-bearer.h +index 336b4442..12ef3cf7 100644 +--- a/src/mm-base-bearer.h ++++ b/src/mm-base-bearer.h +@@ -151,6 +151,16 @@ struct _MMBaseBearerClass { + + #if defined WITH_SUSPEND_RESUME + ++ /* Terse: stop the unsolicited reports this bearer asked the modem for, ++ * so that they do not wake a suspended host; the quick-resume sync ++ * re-arms them. Optional. */ ++ void (* terse) (MMBaseBearer *bearer, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++ gboolean (* terse_finish) (MMBaseBearer *bearer, ++ GAsyncResult *res, ++ GError **error); ++ + /* Reload connection status: + * + * This method should return the exact connection status of the bearer, and +@@ -236,6 +246,14 @@ gboolean mm_base_bearer_sync_finish (MMBaseBearer *self, + GAsyncResult *res, + GError **error); + ++/* Terse Broadband Bearer (async): quiet the modem's reports before a suspend */ ++void mm_base_bearer_terse (MMBaseBearer *self, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean mm_base_bearer_terse_finish (MMBaseBearer *self, ++ GAsyncResult *res, ++ GError **error); ++ + #endif + + #endif /* MM_BASE_BEARER_H */ +diff --git a/src/mm-bearer-qmi.c b/src/mm-bearer-qmi.c +index 64d983eb..d637b993 100644 +--- a/src/mm-bearer-qmi.c ++++ b/src/mm-bearer-qmi.c +@@ -61,6 +61,9 @@ struct _MMBearerQmiPrivate { + guint packet_service_status_ipv6_indication_id; + guint event_report_ipv6_indication_id; + guint extended_ipv6_config_change_id; ++#if defined WITH_SUSPEND_RESUME ++ gboolean terse; ++#endif + + MMPort *data; + MMPort *link; +@@ -71,6 +74,10 @@ struct _MMBearerQmiPrivate { + GList *pco_list; + }; + ++#if defined WITH_SUSPEND_RESUME ++static void terse_rearm (MMBearerQmi *self); ++#endif ++ + /*****************************************************************************/ + /* Stats */ + +@@ -377,6 +384,11 @@ reload_connection_status (MMBaseBearer *self, + GTask *task; + ConnectionStatusContext *ctx; + ++#if defined WITH_SUSPEND_RESUME ++ /* The quick-resume sync lands here: give the modem its reports back */ ++ terse_rearm (MM_BEARER_QMI (self)); ++#endif ++ + ctx = g_new (ConnectionStatusContext, 1); + ctx->step = CONNECTION_STATUS_CONTEXT_STEP_FIRST; + +@@ -1565,6 +1577,93 @@ cleanup_event_report_unsolicited_events (MMBearerQmi *self, + qmi_message_wds_set_event_report_input_unref (input); + } + ++#if defined WITH_SUSPEND_RESUME ++ ++/*****************************************************************************/ ++/* Terse: the WDS event reports (data bearer technology, data systems, ...) ++ * are the modem's way of telling us about every bearer change, and each one ++ * wakes a suspended host. Drop them before the sleep; the quick-resume ++ * synchronization re-arms them. */ ++ ++static gboolean ++terse_finish (MMBaseBearer *self, ++ GAsyncResult *res, ++ GError **error) ++{ ++ return g_task_propagate_boolean (G_TASK (res), error); ++} ++ ++static void ++terse (MMBaseBearer *_self, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ MMBearerQmi *self = MM_BEARER_QMI (_self); ++ GTask *task; ++ ++ task = g_task_new (self, NULL, callback, user_data); ++ ++ if (self->priv->client_ipv4 && self->priv->event_report_ipv4_indication_id) { ++ cleanup_event_report_unsolicited_events (self, self->priv->client_ipv4, ++ &self->priv->event_report_ipv4_indication_id); ++ self->priv->terse = TRUE; ++ } ++ if (self->priv->client_ipv6 && self->priv->event_report_ipv6_indication_id) { ++ cleanup_event_report_unsolicited_events (self, self->priv->client_ipv6, ++ &self->priv->event_report_ipv6_indication_id); ++ self->priv->terse = TRUE; ++ } ++ if (self->priv->terse) ++ mm_obj_msg (self, "terse: WDS event reports disabled"); ++ ++ g_task_return_boolean (task, TRUE); ++ g_object_unref (task); ++} ++ ++static void ++terse_rearm_ready (QmiClientWds *client, ++ GAsyncResult *res, ++ MMBearerQmi *self) ++{ ++ GError *error = NULL; ++ guint id; ++ ++ id = connect_enable_indications_ready (client, res, self, &error); ++ if (!id) { ++ mm_obj_warn (self, "couldn't re-enable WDS event reports after resume: %s", ++ error ? error->message : "unknown error"); ++ g_clear_error (&error); ++ } else if (client == self->priv->client_ipv4) { ++ self->priv->event_report_ipv4_indication_id = id; ++ } else if (client == self->priv->client_ipv6) { ++ self->priv->event_report_ipv6_indication_id = id; ++ } else { ++ /* the bearer was disconnected meanwhile: the client is gone */ ++ g_signal_handler_disconnect (client, id); ++ } ++ g_object_unref (self); ++} ++ ++static void ++terse_rearm (MMBearerQmi *self) ++{ ++ if (!self->priv->terse) ++ return; ++ self->priv->terse = FALSE; ++ ++ if (self->priv->client_ipv4 && !self->priv->event_report_ipv4_indication_id) ++ setup_event_report_unsolicited_events (self, self->priv->client_ipv4, NULL, ++ (GAsyncReadyCallback)terse_rearm_ready, ++ g_object_ref (self)); ++ if (self->priv->client_ipv6 && !self->priv->event_report_ipv6_indication_id) ++ setup_event_report_unsolicited_events (self, self->priv->client_ipv6, NULL, ++ (GAsyncReadyCallback)terse_rearm_ready, ++ g_object_ref (self)); ++ mm_obj_msg (self, "terse: WDS event reports re-enabled"); ++} ++ ++#endif ++ + static void + set_ip_family_ready (QmiClientWds *client, + GAsyncResult *res, +@@ -3015,5 +3114,7 @@ mm_bearer_qmi_class_init (MMBearerQmiClass *klass) + #if defined WITH_SUSPEND_RESUME + base_bearer_class->reload_connection_status = reload_connection_status; + base_bearer_class->reload_connection_status_finish = reload_connection_status_finish; ++ base_bearer_class->terse = terse; ++ base_bearer_class->terse_finish = terse_finish; + #endif + } +diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c +index 9364a20b..e65e4260 100644 +--- a/src/mm-broadband-modem.c ++++ b/src/mm-broadband-modem.c +@@ -13193,11 +13193,13 @@ synchronize (MMBaseModem *self, + typedef enum { + TERSE_STEP_FIRST, + TERSE_STEP_DISABLE_UNSOLICITED_EVENTS_3GPP, ++ TERSE_STEP_BEARERS, + TERSE_STEP_LAST, + } TerseStep; + + typedef struct { + TerseStep step; ++ guint pending_bearers; + } TerseContext; + + static void terse_step (GTask *task); +@@ -13228,6 +13230,38 @@ iface_modem_3gpp_terse_ready (MMIfaceModem3gpp *self, + terse_step (task); + } + ++static void ++bearer_terse_ready (MMBaseBearer *bearer, ++ GAsyncResult *res, ++ GTask *task) ++{ ++ MMBroadbandModem *self; ++ TerseContext *ctx; ++ g_autoptr(GError) error = NULL; ++ ++ self = g_task_get_source_object (task); ++ ctx = g_task_get_task_data (task); ++ ++ if (!mm_base_bearer_terse_finish (bearer, res, &error)) ++ mm_obj_warn (self, "bearer terse failed: %s", error->message); ++ ++ if (--ctx->pending_bearers == 0) { ++ ctx->step++; ++ terse_step (task); ++ } ++} ++ ++static void ++bearer_terse_start (MMBaseBearer *bearer, ++ GTask *task) ++{ ++ TerseContext *ctx; ++ ++ ctx = g_task_get_task_data (task); ++ ctx->pending_bearers++; ++ mm_base_bearer_terse (bearer, (GAsyncReadyCallback)bearer_terse_ready, task); ++} ++ + static void + terse_step (GTask *task) + { +@@ -13253,6 +13287,21 @@ terse_step (GTask *task) + ctx->step++; + /* fall through */ + ++ case TERSE_STEP_BEARERS: { ++ g_autoptr(MMBearerList) list = NULL; ++ ++ g_object_get (self, MM_IFACE_MODEM_BEARER_LIST, &list, NULL); ++ if (list) { ++ mm_obj_msg (self, "terse state (%d/%d): bearers disable unsolicited events", ++ ctx->step, TERSE_STEP_LAST); ++ /* hold the count while iterating: a bearer finishing early must not step on */ ++ ctx->pending_bearers = 1; ++ mm_bearer_list_foreach (list, (MMBearerListForeachFunc)bearer_terse_start, task); ++ if (--ctx->pending_bearers > 0) ++ return; ++ } ++ ctx->step++; ++ } /* fall through */ + case TERSE_STEP_LAST: + mm_obj_msg (self, "setting terse state (%d/%d): all done", + ctx->step, TERSE_STEP_LAST); +-- +2.55.0 + diff --git a/aports/temp/modemmanager/APKBUILD b/aports/temp/modemmanager/APKBUILD index d5ef067..e8ec425 100644 --- a/aports/temp/modemmanager/APKBUILD +++ b/aports/temp/modemmanager/APKBUILD @@ -8,7 +8,7 @@ maintainer="Achill Gilgenast " pkgname=modemmanager pkgver=1.25.95_git20261005 -pkgrel=101 +pkgrel=102 _commit=7f0bf17ab518cd78925cf7e53cc0d5c42f88b41a pkgdesc="ModemManager library" url="https://www.freedesktop.org/wiki/Software/ModemManager" @@ -73,6 +73,7 @@ source="ModemManager-$_commit.tar.gz 0001-intel-XMM7360-fix-big-endian-handling-of-ASN-integer.patch 0002-shared-qmi-unlock-AFW-gated-GNSS-engines-at-LOC-star.patch 0003-shared-qmi-derive-location-from-Position-Report-indi.patch + 0004-bearer-quiet-the-WDS-event-reports-in-terse-re-arm-t.patch " #builddir="$srcdir"/ModemManager-$pkgver builddir="$srcdir"/ModemManager-$_commit @@ -125,4 +126,5 @@ fd659330b2ec4e8368807053ec01498241dec153f6f2491bb17c42e2b81c8d9a98ff5f8c153874a6 6fab86c27502b29be0c6610d835a3249a4a81993eb986cff2c3ea9393fadd3f693ba4bb0532ae0e50a83d359559511859dd05f7ea2f0cb52839f535a7e49f8a0 modemmanager.rules 6e234cebcdec6d1afbca164ee5c13c96f7132ef31231d5c043cc5750ebe99e362a384d0463874b3c1c30799b433e0d3286add03af13c97fa406b4b5d2b78880c modemmanager.initd f06e9b89acab5b61c5965658d62551b0f168066384674f7c4dd56feb6f811c82c640470e634e32d8c4ef62290588cf974aa5f213e5c67e797a06d33e61fa24ec 0001-intel-XMM7360-fix-big-endian-handling-of-ASN-integer.patch +1085a27a7f5dd89c5e4851ae3ddb2d6d970abbb3e2c0c548b4254c2aa13d702bff2234d03fb8c73662050d43508374cb4feb22166298a8dd2a6480bb77579b6f 0004-bearer-quiet-the-WDS-event-reports-in-terse-re-arm-t.patch "