aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Williams <dan@ioncontrol.co>2025-05-08 20:06:24 -0500
committerDan Williams <dan@ioncontrol.co>2025-05-08 20:06:24 -0500
commit02942a04f95f64cda36360c72961fe0e20459ad4 (patch)
tree1a510878341cffdc28908958e4d30bff0621a298
parentbf416a7462d1cde66dee213d858dd2b8d06f042a (diff)
parent0bfb11c62cc76d539d5d60945d6d810fddbf57c6 (diff)
Merge request !1314 from 'quectel-power-down'
quectel: wait for POWERED DOWN URC and decrease other power operation timeouts https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/merge_requests/1314
-rw-r--r--src/plugins/quectel/mm-broadband-modem-quectel.c233
-rw-r--r--src/plugins/quectel/mm-broadband-modem-quectel.h3
2 files changed, 224 insertions, 12 deletions
diff --git a/src/plugins/quectel/mm-broadband-modem-quectel.c b/src/plugins/quectel/mm-broadband-modem-quectel.c
index ecd7c9c1..072e291b 100644
--- a/src/plugins/quectel/mm-broadband-modem-quectel.c
+++ b/src/plugins/quectel/mm-broadband-modem-quectel.c
@@ -21,6 +21,7 @@
#include "mm-iface-modem-firmware.h"
#include "mm-iface-modem-location.h"
#include "mm-iface-modem-time.h"
+#include "mm-log-object.h"
#include "mm-shared-quectel.h"
#include "mm-base-modem-at.h"
@@ -41,6 +42,19 @@ G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQuectel, mm_broadband_modem_quectel, MM_
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_QUECTEL, shared_quectel_init))
+struct _MMBroadbandModemQuectelPrivate {
+ GRegex *powered_down_regex;
+};
+
+#define MM_BROADBAND_MODEM_QUECTEL_POWERED_DOWN "powered-down"
+
+enum {
+ POWERED_DOWN,
+ LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
/*****************************************************************************/
/* Power state loading (Modem interface) */
@@ -99,7 +113,150 @@ load_power_state (MMIfaceModem *self,
}
/*****************************************************************************/
-/* Modem power up/down/off (Modem interface) */
+/* POWERED DOWN unsolicited event handler */
+
+static void
+powered_down_handler (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModem *self)
+{
+ /* The POWERED DOWN URC indicates the modem is ready to be powered down */
+ g_signal_emit (self, signals[POWERED_DOWN], 0);
+}
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+typedef struct {
+ MMBroadbandModemQuectel *modem;
+ guint urc_id;
+ guint timeout_id;
+} PowerDownContext;
+
+static void
+power_down_context_clear_timeout (PowerDownContext *ctx)
+{
+ if (ctx->timeout_id) {
+ g_source_remove (ctx->timeout_id);
+ ctx->timeout_id = 0;
+ }
+}
+
+static void
+power_down_context_disconnect_urc (PowerDownContext *ctx)
+{
+ if (ctx->urc_id) {
+ g_signal_handler_disconnect (ctx->modem, ctx->urc_id);
+ ctx->urc_id = 0;
+ }
+}
+
+static void
+power_down_context_free (PowerDownContext *ctx)
+{
+ g_assert (!ctx->urc_id);
+ g_assert (!ctx->timeout_id);
+ g_clear_object (&ctx->modem);
+ g_slice_free (PowerDownContext, ctx);
+}
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+power_off_powered_down (MMBroadbandModemQuectel *self,
+ GTask *task)
+{
+ PowerDownContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ mm_obj_dbg (self, "got POWERED DOWN URC; proceeding with power off");
+ power_down_context_clear_timeout (ctx);
+ power_down_context_disconnect_urc (ctx);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static gboolean
+powered_down_timeout (GTask *task)
+{
+ PowerDownContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ power_down_context_clear_timeout (ctx);
+ power_down_context_disconnect_urc (ctx);
+
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_TIMEOUT,
+ "timed out waiting for POWERED DOWN URC");
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+power_off_ready (MMBroadbandModemQuectel *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+
+ /* Docs for many devices state that +QPOWD's OK response comes very
+ * quickly but caller must wait for a "POWERED DOWN" URC before allowing
+ * modem power to be cut.
+ */
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Wait for the POWERED DOWN URC */
+ mm_obj_dbg (self, "waiting for POWERED DOWN URC...");
+}
+
+static void
+modem_power_off (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemQuectel *self = MM_BROADBAND_MODEM_QUECTEL (_self);
+ GTask *task;
+ PowerDownContext *ctx;
+
+ task = g_task_new (self,
+ mm_base_modem_peek_cancellable (MM_BASE_MODEM (self)),
+ callback,
+ user_data);
+
+ ctx = g_slice_new0 (PowerDownContext);
+ ctx->modem = g_object_ref (self);
+ ctx->urc_id = g_signal_connect (self,
+ MM_BROADBAND_MODEM_QUECTEL_POWERED_DOWN,
+ (GCallback)power_off_powered_down,
+ task);
+ /* Docs state caller must wait up to 60 seconds for POWERED DOWN URC */
+ ctx->timeout_id = g_timeout_add_seconds (62,
+ (GSourceFunc)powered_down_timeout,
+ task);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)power_down_context_free);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+QPOWD=1",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)power_off_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Modem power up/off (Modem interface) */
static gboolean
common_modem_power_operation_finish (MMIfaceModem *self,
@@ -117,7 +274,7 @@ common_modem_power_operation (MMBroadbandModemQuectel *self,
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
command,
- 30,
+ 15,
FALSE,
callback,
user_data);
@@ -132,14 +289,6 @@ modem_reset (MMIfaceModem *self,
}
static void
-modem_power_off (MMIfaceModem *self,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- common_modem_power_operation (MM_BROADBAND_MODEM_QUECTEL (self), "+QPOWD=1", callback, user_data);
-}
-
-static void
modem_power_down (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
@@ -157,6 +306,32 @@ modem_power_up (MMIfaceModem *self,
/*****************************************************************************/
+static void
+setup_ports (MMBroadbandModem *_self)
+{
+ MMBroadbandModemQuectel *self = MM_BROADBAND_MODEM_QUECTEL (_self);
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ mm_shared_quectel_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));
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (ports[i]) {
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->powered_down_regex,
+ (MMPortSerialAtUnsolicitedMsgFn)powered_down_handler,
+ self,
+ NULL);
+ }
+ }
+}
+
+/*****************************************************************************/
+
MMBroadbandModemQuectel *
mm_broadband_modem_quectel_new (const gchar *device,
const gchar *physdev,
@@ -182,6 +357,13 @@ mm_broadband_modem_quectel_new (const gchar *device,
static void
mm_broadband_modem_quectel_init (MMBroadbandModemQuectel *self)
{
+ /* Initialize opaque pointer to private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_QUECTEL,
+ MMBroadbandModemQuectelPrivate);
+
+ self->priv->powered_down_regex = g_regex_new ("\\r\\nPOWERED DOWN", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_assert (self->priv->powered_down_regex);
}
static void
@@ -197,7 +379,7 @@ iface_modem_init (MMIfaceModemInterface *iface)
iface->modem_power_up = modem_power_up;
iface->modem_power_up_finish = common_modem_power_operation_finish;
iface->modem_power_down = modem_power_down;
- iface->modem_power_down_finish = common_modem_power_operation_finish;
+ iface->modem_power_down_finish = modem_power_down_finish;
iface->modem_power_off = modem_power_off;
iface->modem_power_off_finish = common_modem_power_operation_finish;
iface->reset = modem_reset;
@@ -267,9 +449,36 @@ shared_quectel_init (MMSharedQuectelInterface *iface)
}
static void
+finalize (GObject *object)
+{
+ MMBroadbandModemQuectel *self = MM_BROADBAND_MODEM_QUECTEL (object);
+
+ g_regex_unref (self->priv->powered_down_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_quectel_parent_class)->finalize (object);
+}
+
+static void
mm_broadband_modem_quectel_class_init (MMBroadbandModemQuectelClass *klass)
{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
- broadband_modem_class->setup_ports = mm_shared_quectel_setup_ports;
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemQuectelPrivate));
+
+ /* Virtual methods */
+ object_class->finalize = finalize;
+
+ broadband_modem_class->setup_ports = setup_ports;
+
+ signals[POWERED_DOWN] = g_signal_new (MM_BROADBAND_MODEM_QUECTEL_POWERED_DOWN,
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, /* accumulator */
+ NULL, /* accumulator data */
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 0,
+ NULL);
}
diff --git a/src/plugins/quectel/mm-broadband-modem-quectel.h b/src/plugins/quectel/mm-broadband-modem-quectel.h
index 2067ff10..56f22d23 100644
--- a/src/plugins/quectel/mm-broadband-modem-quectel.h
+++ b/src/plugins/quectel/mm-broadband-modem-quectel.h
@@ -27,9 +27,12 @@
typedef struct _MMBroadbandModemQuectel MMBroadbandModemQuectel;
typedef struct _MMBroadbandModemQuectelClass MMBroadbandModemQuectelClass;
+typedef struct _MMBroadbandModemQuectelPrivate MMBroadbandModemQuectelPrivate;
struct _MMBroadbandModemQuectel {
MMBroadbandModem parent;
+
+ MMBroadbandModemQuectelPrivate *priv;
};
struct _MMBroadbandModemQuectelClass{