blob: ed3540630de884783be9f9abbd53af6ce0574ac9 [file] [log] [blame]
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2011 Aleksander Morgado <aleksander@gnu.org>
*/
#include <glib.h>
#include <glib-object.h>
#include <ModemManager.h>
#include "mm-base-modem-at.h"
#include "mm-errors-types.h"
static gboolean
abort_task_if_port_unusable (MMBaseModem *self,
MMPortSerialAt *port,
GTask *task)
{
GError *error = NULL;
gboolean init_sequence_enabled = FALSE;
/* If no port given, probably the port disappeared */
if (!port) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_NOT_FOUND,
"Cannot run sequence: port not given");
g_object_unref (task);
return FALSE;
}
/* Ensure we don't try to use a connected port */
if (mm_port_get_connected (MM_PORT (port))) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_CONNECTED,
"Cannot run sequence: port is connected");
g_object_unref (task);
return FALSE;
}
/* Temporarily disable init sequence if we're just sending a
* command to a just opened port */
g_object_get (port, MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, &init_sequence_enabled, NULL);
g_object_set (port, MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, FALSE, NULL);
/* Ensure we have a port open during the sequence */
if (!mm_port_serial_open (MM_PORT_SERIAL (port), &error)) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_CONNECTED,
"Cannot run sequence: '%s'",
error->message);
g_error_free (error);
g_object_unref (task);
return FALSE;
}
/* Reset previous init sequence state */
g_object_set (port, MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, init_sequence_enabled, NULL);
return TRUE;
}
static void
parent_cancellable_cancelled (GCancellable *parent_cancellable,
GCancellable *cancellable)
{
g_cancellable_cancel (cancellable);
}
/*****************************************************************************/
/* AT sequence handling */
typedef struct {
MMPortSerialAt *port;
gulong cancelled_id;
GCancellable *parent_cancellable;
const MMBaseModemAtCommand *current;
const MMBaseModemAtCommand *sequence;
gpointer response_processor_context;
GDestroyNotify response_processor_context_free;
GVariant *result;
} AtSequenceContext;
static void
at_sequence_context_free (AtSequenceContext *ctx)
{
mm_port_serial_close (MM_PORT_SERIAL (ctx->port));
g_object_unref (ctx->port);
if (ctx->response_processor_context &&
ctx->response_processor_context_free)
ctx->response_processor_context_free (ctx->response_processor_context);
if (ctx->parent_cancellable) {
g_cancellable_disconnect (ctx->parent_cancellable,
ctx->cancelled_id);
g_object_unref (ctx->parent_cancellable);
}
if (ctx->result)
g_variant_unref (ctx->result);
g_free (ctx);
}
GVariant *
mm_base_modem_at_sequence_full_finish (MMBaseModem *self,
GAsyncResult *res,
gpointer *response_processor_context,
GError **error)
{
GTask *task;
GVariant *result;
task = G_TASK (res);
result = g_task_propagate_pointer (task, error);
if (response_processor_context && !g_task_had_error (task)) {
AtSequenceContext *ctx;
ctx = g_task_get_task_data (task);
/* transfer none, no need to free the context ourselves, if
* we gave a response_processor_context_free callback */
*response_processor_context = ctx->response_processor_context;
}
/* transfer-none! (so that we can ignore it) */
return result;
}
static void
at_sequence_parse_response (MMPortSerialAt *port,
GAsyncResult *res,
GTask *task)
{
MMBaseModemAtResponseProcessorResult processor_result;
GVariant *result = NULL;
GError *result_error = NULL;
AtSequenceContext *ctx;
g_autofree gchar *response = NULL;
GError *error = NULL;
response = mm_port_serial_at_command_finish (port, res, &error);
/* Cancelled? */
if (g_task_return_error_if_cancelled (task)) {
g_object_unref (task);
return;
}
ctx = g_task_get_task_data (task);
if (!ctx->current->response_processor)
processor_result = MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
else {
const MMBaseModemAtCommand *next = ctx->current + 1;
/* Response processor will tell us if we need to keep on the sequence */
processor_result = ctx->current->response_processor (g_task_get_source_object (task),
ctx->response_processor_context,
ctx->current->command,
response,
next->command ? FALSE : TRUE, /* Last command in sequence? */
error,
&result,
&result_error);
switch (processor_result) {
case MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE:
g_assert (!result && !result_error);
break;
case MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS:
g_assert (!result_error); /* result is optional */
break;
case MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE:
/* On failure, complete with error right away */
g_assert (!result && result_error); /* result is optional */
g_task_return_error (task, result_error);
g_object_unref (task);
if (error)
g_error_free (error);
return;
default:
g_assert_not_reached ();
}
}
if (error)
g_error_free (error);
if (processor_result == MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE) {
ctx->current++;
if (ctx->current->command) {
/* Schedule the next command in the probing group */
mm_port_serial_at_command (
ctx->port,
ctx->current->command,
ctx->current->timeout,
FALSE,
ctx->current->allow_cached,
g_task_get_cancellable (task),
(GAsyncReadyCallback)at_sequence_parse_response,
task);
return;
}
/* On last command, end. */
}
/* If we got a response, set it as result */
if (result)
/* transfer-full */
ctx->result = result;
/* transfer-none, the result remains owned by the GTask context */
g_task_return_pointer (task, ctx->result, NULL);
g_object_unref (task);
}
static void
at_sequence_common (MMBaseModem *self,
MMPortSerialAt *port,
const MMBaseModemAtCommand *sequence,
gpointer response_processor_context,
GDestroyNotify response_processor_context_free,
GTask *task,
GCancellable *parent_cancellable)
{
AtSequenceContext *ctx;
/* Ensure that we have an open port */
if (!abort_task_if_port_unusable (self, port, task))
return;
/* Setup context */
ctx = g_new0 (AtSequenceContext, 1);
ctx->port = g_object_ref (port);
ctx->current = ctx->sequence = sequence;
ctx->response_processor_context = response_processor_context;
ctx->response_processor_context_free = response_processor_context_free;
/* Ensure the cancellable that's already associated with the modem
* will also get cancelled if the modem wide-one gets cancelled */
if (parent_cancellable) {
GCancellable *cancellable;
cancellable = g_task_get_cancellable (task);
ctx->parent_cancellable = g_object_ref (parent_cancellable);
ctx->cancelled_id = g_cancellable_connect (ctx->parent_cancellable,
G_CALLBACK (parent_cancellable_cancelled),
cancellable,
NULL);
}
g_task_set_task_data (task, ctx, (GDestroyNotify)at_sequence_context_free);
/* Go on with the first one in the sequence */
mm_port_serial_at_command (
ctx->port,
ctx->current->command,
ctx->current->timeout,
FALSE,
ctx->current->allow_cached,
g_task_get_cancellable (task),
(GAsyncReadyCallback)at_sequence_parse_response,
task);
}
void
mm_base_modem_at_sequence_full (MMBaseModem *self,
MMPortSerialAt *port,
const MMBaseModemAtCommand *sequence,
gpointer response_processor_context,
GDestroyNotify response_processor_context_free,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GCancellable *modem_cancellable;
GTask *task;
modem_cancellable = mm_base_modem_peek_cancellable (self);
task = g_task_new (self,
cancellable ? cancellable : modem_cancellable,
callback,
user_data);
at_sequence_common (self,
port,
sequence,
response_processor_context,
response_processor_context_free,
task,
cancellable ? modem_cancellable : NULL);
}
GVariant *
mm_base_modem_at_sequence_finish (MMBaseModem *self,
GAsyncResult *res,
gpointer *response_processor_context,
GError **error)
{
return (mm_base_modem_at_sequence_full_finish (
self,
res,
response_processor_context,
error));
}
void
mm_base_modem_at_sequence (MMBaseModem *self,
const MMBaseModemAtCommand *sequence,
gpointer response_processor_context,
GDestroyNotify response_processor_context_free,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMPortSerialAt *port;
GError *error = NULL;
GTask *task;
task = g_task_new (self,
mm_base_modem_peek_cancellable (self),
callback,
user_data);
/* No port given, so we'll try to guess which is best */
port = mm_base_modem_peek_best_at_port (self, &error);
if (!port) {
g_assert (error != NULL);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
at_sequence_common (self,
port,
sequence,
response_processor_context,
response_processor_context_free,
task,
NULL);
}
/*****************************************************************************/
/* Response processor helpers */
MMBaseModemAtResponseProcessorResult
mm_base_modem_response_processor_string (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
if (error) {
*result = NULL;
*result_error = g_error_copy (error);
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
}
*result = g_variant_new_string (response);
*result_error = NULL;
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
}
MMBaseModemAtResponseProcessorResult
mm_base_modem_response_processor_no_result (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
if (error) {
*result = NULL;
*result_error = g_error_copy (error);
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
}
*result = NULL;
*result_error = NULL;
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
}
MMBaseModemAtResponseProcessorResult
mm_base_modem_response_processor_no_result_continue (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
*result = NULL;
if (error) {
*result_error = g_error_copy (error);
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
}
*result_error = NULL;
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
}
MMBaseModemAtResponseProcessorResult
mm_base_modem_response_processor_continue_on_error (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
*result = NULL;
*result_error = NULL;
return (error ?
MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE :
MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS);
}
MMBaseModemAtResponseProcessorResult
mm_base_modem_response_processor_string_ignore_at_errors (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
if (error) {
*result = NULL;
/* Ignore AT errors (ie, ERROR or CMx ERROR) */
if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) {
*result_error = g_error_copy (error);
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
}
*result_error = NULL;
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
}
*result = g_variant_new_string (response);
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
}
/*****************************************************************************/
/* Single AT command handling */
typedef struct {
MMPortSerialAt *port;
gulong cancelled_id;
GCancellable *parent_cancellable;
gchar *response;
} AtCommandContext;
static void
at_command_context_free (AtCommandContext *ctx)
{
mm_port_serial_close (MM_PORT_SERIAL (ctx->port));
if (ctx->parent_cancellable) {
g_cancellable_disconnect (ctx->parent_cancellable,
ctx->cancelled_id);
g_object_unref (ctx->parent_cancellable);
}
g_object_unref (ctx->port);
g_free (ctx->response);
g_free (ctx);
}
const gchar *
mm_base_modem_at_command_full_finish (MMBaseModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
at_command_ready (MMPortSerialAt *port,
GAsyncResult *res,
GTask *task)
{
AtCommandContext *ctx;
GError *error = NULL;
ctx = g_task_get_task_data (task);
g_assert (!ctx->response);
ctx->response = mm_port_serial_at_command_finish (port, res, &error);
/* Cancelled? */
if (g_task_return_error_if_cancelled (task)) {
if (error)
g_error_free (error);
}
/* Error coming from the serial port? */
else if (error)
g_task_return_error (task, error);
/* Valid string response */
else if (ctx->response)
/* transfer-none, the response remains owned by the GTask context */
g_task_return_pointer (task, ctx->response, NULL);
else
g_assert_not_reached ();
g_object_unref (task);
}
static void
at_command_common (MMBaseModem *self,
MMPortSerialAt *port,
const gchar *command,
guint timeout,
gboolean allow_cached,
gboolean is_raw,
GTask *task,
GCancellable *parent_cancellable)
{
AtCommandContext *ctx;
/* Ensure that we have an open port */
if (!abort_task_if_port_unusable (self, port, task))
return;
ctx = g_new0 (AtCommandContext, 1);
ctx->port = g_object_ref (port);
/* Ensure the cancellable that's already associated with the modem
* will also get cancelled if the modem wide-one gets cancelled */
if (parent_cancellable) {
GCancellable *cancellable;
cancellable = g_task_get_cancellable (task);
ctx->parent_cancellable = g_object_ref (parent_cancellable);
ctx->cancelled_id = g_cancellable_connect (ctx->parent_cancellable,
G_CALLBACK (parent_cancellable_cancelled),
cancellable,
NULL);
}
g_task_set_task_data (task, ctx, (GDestroyNotify)at_command_context_free);
/* Go on with the command */
mm_port_serial_at_command (
port,
command,
timeout,
is_raw,
allow_cached,
g_task_get_cancellable (task),
(GAsyncReadyCallback)at_command_ready,
task);
}
void
mm_base_modem_at_command_full (MMBaseModem *self,
MMPortSerialAt *port,
const gchar *command,
guint timeout,
gboolean allow_cached,
gboolean is_raw,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GCancellable *modem_cancellable;
GTask *task;
modem_cancellable = mm_base_modem_peek_cancellable (self);
task = g_task_new (self,
cancellable ? cancellable : modem_cancellable,
callback,
user_data);
at_command_common (self,
port,
command,
timeout,
allow_cached,
is_raw,
task,
cancellable ? modem_cancellable : NULL);
}
const gchar *
mm_base_modem_at_command_finish (MMBaseModem *self,
GAsyncResult *res,
GError **error)
{
return mm_base_modem_at_command_full_finish (self, res, error);
}
static void
_at_command (MMBaseModem *self,
const gchar *command,
guint timeout,
gboolean allow_cached,
gboolean is_raw,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMPortSerialAt *port;
GError *error = NULL;
GTask *task;
task = g_task_new (self,
mm_base_modem_peek_cancellable (self),
callback,
user_data);
/* No port given, so we'll try to guess which is best */
port = mm_base_modem_peek_best_at_port (self, &error);
if (!port) {
g_assert (error != NULL);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
at_command_common (self,
port,
command,
timeout,
allow_cached,
is_raw,
task,
NULL);
}
void
mm_base_modem_at_command (MMBaseModem *self,
const gchar *command,
guint timeout,
gboolean allow_cached,
GAsyncReadyCallback callback,
gpointer user_data)
{
_at_command (self, command, timeout, allow_cached, FALSE, callback, user_data);
}
void
mm_base_modem_at_command_raw (MMBaseModem *self,
const gchar *command,
guint timeout,
gboolean allow_cached,
GAsyncReadyCallback callback,
gpointer user_data)
{
_at_command (self, command, timeout, allow_cached, TRUE, callback, user_data);
}
void
mm_base_modem_at_command_alloc_clear (MMBaseModemAtCommandAlloc *command)
{
g_free (command->command);
}