diff options
author | Aleksander Morgado <aleksander@lanedo.com> | 2012-08-21 17:06:19 +0200 |
---|---|---|
committer | Aleksander Morgado <aleksander@lanedo.com> | 2012-08-23 19:10:59 +0200 |
commit | 0a4eca84899235b12cd8a6480634bd6043dd1e76 (patch) | |
tree | 3db35351bd22a127e3b206f4b6f2ef7412e6842d | |
parent | 2b6755fed5fbf1f25d54de3bf73d36a03a152ee4 (diff) |
bearer-qmi: implement connection/disconnection sequences
-rw-r--r-- | src/mm-bearer-qmi.c | 490 |
1 files changed, 489 insertions, 1 deletions
diff --git a/src/mm-bearer-qmi.c b/src/mm-bearer-qmi.c index c9455d33..0563bc50 100644 --- a/src/mm-bearer-qmi.c +++ b/src/mm-bearer-qmi.c @@ -22,18 +22,485 @@ #include <ModemManager.h> #include <libmm-common.h> +#include <libqmi-glib.h> #include "mm-bearer-qmi.h" #include "mm-utils.h" +#include "mm-serial-enums-types.h" #include "mm-log.h" G_DEFINE_TYPE (MMBearerQmi, mm_bearer_qmi, MM_TYPE_BEARER); struct _MMBearerQmiPrivate { - gpointer dummy; + /* State kept while connected */ + QmiClientWds *client; + MMPort *data; + guint32 packet_data_handle; }; /*****************************************************************************/ +/* Connect */ + +typedef struct { + MMPort *data; + MMBearerIpConfig *ipv4_config; + MMBearerIpConfig *ipv6_config; +} ConnectResult; + +static void +connect_result_free (ConnectResult *result) +{ + if (result->ipv4_config) + g_object_unref (result->ipv4_config); + if (result->ipv6_config) + g_object_unref (result->ipv6_config); + g_object_unref (result->data); + g_slice_free (ConnectResult, result); +} + +typedef enum { + CONNECT_STEP_FIRST, + CONNECT_STEP_OPEN_QMI_PORT, + CONNECT_STEP_WDS_CLIENT, + CONNECT_STEP_START_NETWORK, + CONNECT_STEP_LAST +} ConnectStep; + +typedef struct { + MMBearerQmi *self; + GSimpleAsyncResult *result; + GCancellable *cancellable; + ConnectStep step; + MMPort *data; + MMQmiPort *qmi; + QmiClientWds *client; + guint32 packet_data_handle; +} ConnectContext; + +static void +connect_context_complete_and_free (ConnectContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + if (ctx->client) + g_object_unref (ctx->client); + g_object_unref (ctx->data); + g_object_unref (ctx->qmi); + g_object_unref (ctx->cancellable); + g_object_unref (ctx->result); + g_object_unref (ctx->self); + g_slice_free (ConnectContext, ctx); +} + +static gboolean +connect_finish (MMBearer *self, + GAsyncResult *res, + MMPort **data, + MMBearerIpConfig **ipv4_config, + MMBearerIpConfig **ipv6_config, + GError **error) +{ + ConnectResult *result; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return FALSE; + + result = (ConnectResult *) g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); + *data = MM_PORT (g_object_ref (result->data)); + *ipv4_config = (result->ipv4_config ? g_object_ref (result->ipv4_config) : NULL); + *ipv6_config = (result->ipv6_config ? g_object_ref (result->ipv6_config) : NULL); + + return TRUE; +} + +static void connect_context_step (ConnectContext *ctx); + +static void +start_network_ready (QmiClientWds *client, + GAsyncResult *res, + ConnectContext *ctx) +{ + GError *error = NULL; + QmiMessageWdsStartNetworkOutput *output; + + output = qmi_client_wds_start_network_finish (client, res, &error); + if (!output) { + g_simple_async_result_take_error (ctx->result, error); + connect_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_wds_start_network_output_get_result (output, &error)) { + mm_info ("error: couldn't start network: %s", error->message); + if (g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_CALL_FAILED)) { + guint16 cer; + guint16 verbose_cer_type; + guint16 verbose_cer_reason; + + if (qmi_message_wds_start_network_output_get_call_end_reason ( + output, + &cer, + NULL)) + mm_info ("call end reason: %u", cer); + + if (qmi_message_wds_start_network_output_get_verbose_call_end_reason ( + output, + &verbose_cer_type, + &verbose_cer_reason, + NULL)) + mm_info ("verbose call end reason: %u, %u", + verbose_cer_type, + verbose_cer_reason); + } + + g_simple_async_result_take_error (ctx->result, error); + connect_context_complete_and_free (ctx); + qmi_message_wds_start_network_output_unref (output); + return; + } + + qmi_message_wds_start_network_output_get_packet_data_handle (output, &ctx->packet_data_handle, NULL); + qmi_message_wds_start_network_output_unref (output); + + /* Keep on */ + ctx->step++; + connect_context_step (ctx); +} + +static void +qmi_port_allocate_client_ready (MMQmiPort *qmi, + GAsyncResult *res, + ConnectContext *ctx) +{ + GError *error = NULL; + + if (!mm_qmi_port_allocate_client_finish (qmi, res, &error)) { + g_simple_async_result_take_error (ctx->result, error); + connect_context_complete_and_free (ctx); + return; + } + + ctx->client = QMI_CLIENT_WDS (mm_qmi_port_get_client (qmi, QMI_SERVICE_WDS)); + + /* Keep on */ + ctx->step++; + connect_context_step (ctx); +} + +static void +qmi_port_open_ready (MMQmiPort *qmi, + GAsyncResult *res, + ConnectContext *ctx) +{ + GError *error = NULL; + + if (!mm_qmi_port_open_finish (qmi, res, &error)) { + g_simple_async_result_take_error (ctx->result, error); + connect_context_complete_and_free (ctx); + return; + } + + /* Keep on */ + ctx->step++; + connect_context_step (ctx); +} + +static void +connect_context_step (ConnectContext *ctx) +{ + /* If cancelled, complete */ + if (g_cancellable_is_cancelled (ctx->cancellable)) { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_CANCELLED, + "Connection setup operation has been cancelled"); + connect_context_complete_and_free (ctx); + return; + } + + switch (ctx->step) { + case CONNECT_STEP_FIRST: + /* Fall down */ + ctx->step++; + + case CONNECT_STEP_OPEN_QMI_PORT: + if (!mm_qmi_port_is_open (ctx->qmi)) { + mm_qmi_port_open (ctx->qmi, + ctx->cancellable, + (GAsyncReadyCallback)qmi_port_open_ready, + ctx); + return; + } + + /* If already open, just fall down */ + ctx->step++; + + case CONNECT_STEP_WDS_CLIENT: { + QmiClient *client; + + client = mm_qmi_port_get_client (ctx->qmi, QMI_SERVICE_WDS); + if (!client) { + mm_qmi_port_allocate_client (ctx->qmi, + QMI_SERVICE_WDS, + ctx->cancellable, + (GAsyncReadyCallback)qmi_port_allocate_client_ready, + ctx); + return; + } + + ctx->client = QMI_CLIENT_WDS (client); + } + + case CONNECT_STEP_START_NETWORK: + qmi_client_wds_start_network (ctx->client, + NULL, /* allow NULL input for now */ + 10, + ctx->cancellable, + (GAsyncReadyCallback)start_network_ready, + ctx); + return; + + case CONNECT_STEP_LAST: { + MMBearerIpConfig *config; + ConnectResult *result; + + /* Port is connected; update the state */ + mm_port_set_connected (MM_PORT (ctx->data), TRUE); + + /* Keep connection related data */ + g_assert (ctx->self->priv->data == NULL); + ctx->self->priv->data = g_object_ref (ctx->data); + g_assert (ctx->self->priv->client == NULL); + ctx->self->priv->client = g_object_ref (ctx->client); + g_assert (ctx->self->priv->packet_data_handle == 0); + ctx->self->priv->packet_data_handle = ctx->packet_data_handle; + + /* Build IP config; always DHCP based */ + config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP); + + /* Build result */ + result = g_slice_new0 (ConnectResult); + result->data = g_object_ref (ctx->data); + result->ipv4_config = config; + result->ipv6_config = g_object_ref (config); + + /* Set operation result */ + g_simple_async_result_set_op_res_gpointer (ctx->result, + result, + (GDestroyNotify)connect_result_free); + connect_context_complete_and_free (ctx); + } + } +} + +static MMQmiPort * +get_qmi_port_for_data_port (MMBaseModem *modem, + MMPort *data) +{ + /* TODO: match QMI and WWAN ports */ + return mm_base_modem_get_port_qmi (modem); +} + +static void +connect (MMBearer *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ConnectContext *ctx; + MMBaseModem *modem = NULL; + MMPort *data; + MMQmiPort *qmi; + + g_object_get (self, + MM_BEARER_MODEM, &modem, + NULL); + g_assert (modem); + + /* Grab a data port */ + data = mm_base_modem_get_best_data_port (modem); + if (!data) { + g_simple_async_report_error_in_idle ( + G_OBJECT (self), + callback, + user_data, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No valid data port found to launch connection"); + g_object_unref (modem); + return; + } + + /* Each data port has a single QMI port associated */ + qmi = get_qmi_port_for_data_port (modem, data); + if (!qmi) { + g_simple_async_report_error_in_idle ( + G_OBJECT (self), + callback, + user_data, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No QMI port found associated to data port (%s/%s)", + mm_port_subsys_get_string (mm_port_get_subsys (data)), + mm_port_get_device (data)); + g_object_unref (data); + g_object_unref (modem); + return; + } + + mm_dbg ("Launching connection with QMI port (%s/%s) and data port (%s/%s)", + mm_port_subsys_get_string (mm_port_get_subsys (MM_PORT (qmi))), + mm_port_get_device (MM_PORT (qmi)), + mm_port_subsys_get_string (mm_port_get_subsys (data)), + mm_port_get_device (data)); + + /* In this context, we only keep the stuff we'll need later */ + ctx = g_slice_new0 (ConnectContext); + ctx->self = g_object_ref (self); + ctx->qmi = qmi; + ctx->data = data; + ctx->cancellable = g_object_ref (cancellable); + ctx->step = CONNECT_STEP_FIRST; + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + connect); + + /* Run! */ + connect_context_step (ctx); + g_object_unref (modem); +} + +/*****************************************************************************/ +/* Disconnect */ + +typedef struct { + MMBearerQmi *self; + QmiClientWds *client; + MMPort *data; + guint32 packet_data_handle; + GSimpleAsyncResult *result; +} DisconnectContext; + +static void +disconnect_context_complete_and_free (DisconnectContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->data); + g_object_unref (ctx->self); + g_slice_free (DisconnectContext, ctx); +} + +static gboolean +disconnect_finish (MMBearer *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +reset_bearer_connection (MMBearerQmi *self) +{ + if (self->priv->data) { + /* Port is disconnected; update the state */ + mm_port_set_connected (self->priv->data, FALSE); + g_clear_object (&self->priv->data); + } + + g_clear_object (&self->priv->client); + self->priv->packet_data_handle = 0; +} + +static void +stop_network_ready (QmiClientWds *client, + GAsyncResult *res, + DisconnectContext *ctx) +{ + GError *error = NULL; + QmiMessageWdsStopNetworkOutput *output; + + output = qmi_client_wds_stop_network_finish (client, res, &error); + if (!output) { + g_simple_async_result_take_error (ctx->result, error); + disconnect_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_wds_stop_network_output_get_result (output, &error)) { + qmi_message_wds_stop_network_output_unref (output); + g_simple_async_result_take_error (ctx->result, error); + disconnect_context_complete_and_free (ctx); + return; + } + + /* Clear internal status */ + reset_bearer_connection (ctx->self); + + qmi_message_wds_stop_network_output_unref (output); + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + disconnect_context_complete_and_free (ctx); +} + +static void +disconnect (MMBearer *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBearerQmi *self = MM_BEARER_QMI (_self); + QmiMessageWdsStopNetworkInput *input; + DisconnectContext *ctx; + + if (!self->priv->packet_data_handle || + !self->priv->client || + !self->priv->data) { + g_simple_async_report_error_in_idle ( + G_OBJECT (self), + callback, + user_data, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't disconnect QMI bearer: this bearer is not connected"); + return; + } + + ctx = g_slice_new0 (DisconnectContext); + ctx->self = g_object_ref (self); + ctx->data = g_object_ref (self->priv->data); + ctx->client = g_object_ref (self->priv->client); + ctx->packet_data_handle = self->priv->packet_data_handle; + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + disconnect); + + input = qmi_message_wds_stop_network_input_new (); + qmi_message_wds_stop_network_input_set_packet_data_handle (input, ctx->packet_data_handle, NULL); + qmi_client_wds_stop_network (ctx->client, + input, + 10, + NULL, + (GAsyncReadyCallback)stop_network_ready, + ctx); +} + +/*****************************************************************************/ + +static void +report_disconnection (MMBearer *self) +{ + /* Cleanup all connection related data */ + reset_bearer_connection (MM_BEARER_QMI (self)); + + /* Chain up parent's report_disconection() */ + MM_BEARER_CLASS (mm_bearer_qmi_parent_class)->report_disconnection (self); +} + +/*****************************************************************************/ MMBearer * mm_bearer_qmi_new (MMBroadbandModemQmi *modem, @@ -65,9 +532,30 @@ mm_bearer_qmi_init (MMBearerQmi *self) } static void +dispose (GObject *object) +{ + MMBearerQmi *self = MM_BEARER_QMI (object); + + g_clear_object (&self->priv->data); + g_clear_object (&self->priv->client); + + G_OBJECT_CLASS (mm_bearer_qmi_parent_class)->dispose (object); +} + +static void mm_bearer_qmi_class_init (MMBearerQmiClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBearerClass *bearer_class = MM_BEARER_CLASS (klass); g_type_class_add_private (object_class, sizeof (MMBearerQmiPrivate)); + + /* Virtual methods */ + object_class->dispose = dispose; + + bearer_class->connect = connect; + bearer_class->connect_finish = connect_finish; + bearer_class->disconnect = disconnect; + bearer_class->disconnect_finish = disconnect_finish; + bearer_class->report_disconnection = report_disconnection; } |