diff options
-rw-r--r-- | plugins/cinterion/mm-broadband-modem-cinterion.c | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/plugins/cinterion/mm-broadband-modem-cinterion.c b/plugins/cinterion/mm-broadband-modem-cinterion.c index f86460a5..b5fa5837 100644 --- a/plugins/cinterion/mm-broadband-modem-cinterion.c +++ b/plugins/cinterion/mm-broadband-modem-cinterion.c @@ -277,6 +277,158 @@ modem_power_down (MMIfaceModem *self, } /*****************************************************************************/ +/* Modem Power Off */ + +#define MAX_POWER_OFF_WAIT_TIME_SECS 20 + +typedef struct { + MMBroadbandModemCinterion *self; + MMPortSerialAt *port; + GSimpleAsyncResult *result; + GRegex *shutdown_regex; + gboolean shutdown_received; + gboolean smso_replied; + gboolean serial_open; + guint timeout_id; +} PowerOffContext; + +static void +power_off_context_complete_and_free (PowerOffContext *ctx) +{ + if (ctx->serial_open) + mm_port_serial_close (MM_PORT_SERIAL (ctx->port)); + if (ctx->timeout_id) + g_source_remove (ctx->timeout_id); + mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->shutdown_regex, NULL, NULL, NULL); + g_object_unref (ctx->port); + g_object_unref (ctx->self); + g_regex_unref (ctx->shutdown_regex); + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_slice_free (PowerOffContext, ctx); +} + +static gboolean +modem_power_off_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +complete_power_off (PowerOffContext *ctx) +{ + if (!ctx->shutdown_received || !ctx->smso_replied) + return; + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + power_off_context_complete_and_free (ctx); +} + +static void +smso_ready (MMBaseModem *self, + GAsyncResult *res, + PowerOffContext *ctx) +{ + GError *error = NULL; + + mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, &error); + if (error) { + g_simple_async_result_take_error (ctx->result, error); + power_off_context_complete_and_free (ctx); + return; + } + + /* Set as replied */ + ctx->smso_replied = TRUE; + complete_power_off (ctx); +} + +static void +shutdown_received (MMPortSerialAt *port, + GMatchInfo *match_info, + PowerOffContext *ctx) +{ + /* Cleanup handler */ + mm_port_serial_at_add_unsolicited_msg_handler (port, ctx->shutdown_regex, NULL, NULL, NULL); + /* Set as received */ + ctx->shutdown_received = TRUE; + complete_power_off (ctx); +} + +static gboolean +power_off_timeout_cb (PowerOffContext *ctx) +{ + ctx->timeout_id = 0; + + /* The SMSO reply should have come earlier */ + g_warn_if_fail (ctx->smso_replied == TRUE); + + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Power off operation timed out"); + power_off_context_complete_and_free (ctx); + + return FALSE; +} + +static void +modem_power_off (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + PowerOffContext *ctx; + GError *error = NULL; + + ctx = g_slice_new0 (PowerOffContext); + ctx->self = g_object_ref (self); + ctx->port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_power_off); + ctx->shutdown_regex = g_regex_new ("\\r\\n\\^SHUTDOWN\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + ctx->timeout_id = g_timeout_add_seconds (MAX_POWER_OFF_WAIT_TIME_SECS, + (GSourceFunc)power_off_timeout_cb, + ctx); + + /* We'll need to wait for a ^SHUTDOWN before returning the action, which is + * when the modem tells us that it is ready to be shutdown */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ctx->port, + ctx->shutdown_regex, + (MMPortSerialAtUnsolicitedMsgFn)shutdown_received, + ctx, + NULL); + + /* In order to get the ^SHUTDOWN notification, we must keep the port open + * during the wait time */ + ctx->serial_open = mm_port_serial_open (MM_PORT_SERIAL (ctx->port), &error); + if (G_UNLIKELY (error)) { + g_simple_async_result_take_error (ctx->result, error); + power_off_context_complete_and_free (ctx); + return; + } + + /* Note: we'll use a timeout < MAX_POWER_OFF_WAIT_TIME_SECS for the AT command, + * so we're sure that the AT command reply will always come before the timeout + * fires */ + g_assert (MAX_POWER_OFF_WAIT_TIME_SECS > 5); + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + ctx->port, + "^SMSO", + 5, + FALSE, /* allow_cached */ + FALSE, /* is_raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)smso_ready, + ctx); +} + +/*****************************************************************************/ /* ACCESS TECHNOLOGIES */ static gboolean @@ -1283,6 +1435,8 @@ iface_modem_init (MMIfaceModem *iface) iface->setup_flow_control_finish = setup_flow_control_finish; iface->modem_power_down = modem_power_down; iface->modem_power_down_finish = modem_power_down_finish; + iface->modem_power_off = modem_power_off; + iface->modem_power_off_finish = modem_power_off_finish; } static void |