diff --git a/src/plugins/quectel/mm-broadband-modem-quectel.c b/src/plugins/quectel/mm-broadband-modem-quectel.c index ecd7c9c17d8b3566aa52fb7c5fa5fdb64a6b1507..bdee9c985fb01b5c7a476319ca04f487ab15c4cc 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_msg (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_msg (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); @@ -131,14 +288,6 @@ modem_reset (MMIfaceModem *self, common_modem_power_operation (MM_BROADBAND_MODEM_QUECTEL (self), "+CFUN=1,1", callback, user_data); } -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, @@ -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; @@ -266,10 +448,37 @@ shared_quectel_init (MMSharedQuectelInterface *iface) iface->peek_parent_class = peek_parent_class; } +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 2067ff1083b10f9f88b31b1905634c3d7d04d461..56f22d231f359b0e2b32f9830a07748a305592f6 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{