cinterion: implement power off

Powering off Cinterion modems is done with the AT^SMSO command. Once this
command is sent to the device, ModemManager still needs to wait to get an
unsolicited ^SHUTDOWN message from the modem, which is when the shutdown
is considered complete.

Newer Cinterion devices, though, don't seem to follow this rule and just
reply OK to AT^SMSO (i.e. no ^SHUTDOWN), but instead, they will remove the
tty ports.
This commit is contained in:
Aleksander Morgado
2014-02-18 11:03:33 +01:00
parent 00a924617c
commit f9b9f25cd1

View File

@@ -276,6 +276,158 @@ modem_power_down (MMIfaceModem *self,
result); result);
} }
/*****************************************************************************/
/* 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 */ /* ACCESS TECHNOLOGIES */
@@ -1283,6 +1435,8 @@ iface_modem_init (MMIfaceModem *iface)
iface->setup_flow_control_finish = setup_flow_control_finish; iface->setup_flow_control_finish = setup_flow_control_finish;
iface->modem_power_down = modem_power_down; iface->modem_power_down = modem_power_down;
iface->modem_power_down_finish = modem_power_down_finish; 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 static void