Newer
Older
/* -*- 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) 2016 Trimble Navigation Limited
* Author: Matthew Stanger <matthew_stanger@trimble.com>
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <ModemManager.h>
#include "mm-base-modem-at.h"
#include "mm-broadband-bearer-cinterion.h"
#include "mm-modem-helpers.h"
#include "mm-modem-helpers-cinterion.h"
#include "mm-daemon-enums-types.h"
G_DEFINE_TYPE (MMBroadbandBearerCinterion, mm_broadband_bearer_cinterion, MM_TYPE_BROADBAND_BEARER)
/*****************************************************************************/
/* WWAN interface mapping */
typedef struct {
guint swwan_index;
guint usb_iface_num;
} UsbInterfaceConfig;
/* Map SWWAN index, USB interface number and preferred PDP context.
*
* The expected USB interface mapping is:
* INTERFACE=usb0 -> ID_USB_INTERFACE_NUM=0a
* INTERFACE=usb1 -> ID_USB_INTERFACE_NUM=0c
*/
static const UsbInterfaceConfig usb_interface_configs[] = {
{
.swwan_index = 1,
.usb_iface_num = 0x0a,
},
{
.swwan_index = 2,
.usb_iface_num = 0x0c,
},
};
static gint
get_usb_interface_config_index (MMPort *data,
GError **error)
{
guint usb_iface_num;
guint i;
usb_iface_num = (guint) mm_kernel_device_get_interface_number (mm_port_peek_kernel_device (data));
for (i = 0; i < G_N_ELEMENTS (usb_interface_configs); i++) {
if (usb_interface_configs[i].usb_iface_num == usb_iface_num)
return (gint) i;
}
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Unsupported WWAN interface: unexpected interface number: 0x%02x", usb_iface_num);
return -1;
}
/*****************************************************************************/
/* Connection status loading
* NOTE: only CONNECTED or DISCONNECTED should be reported here.
*/
static MMBearerConnectionStatus
load_connection_status_finish (MMBaseBearer *bearer,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gssize aux;
aux = g_task_propagate_int (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return MM_BEARER_CONNECTION_STATUS_UNKNOWN;
}
return (MMBearerConnectionStatus) aux;
}
static void
swwan_check_status_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
MMBroadbandBearerCinterion *self;
const gchar *response;
GError *error = NULL;
MMBearerConnectionStatus status;
guint cid;
self = g_task_get_source_object (task);
cid = GPOINTER_TO_UINT (g_task_get_task_data (task));
response = mm_base_modem_at_command_finish (modem, res, &error);
if (!response) {
g_task_return_error (task, error);
goto out;
}
status = mm_cinterion_parse_swwan_response (response, cid, self, &error);
if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) {
g_task_return_error (task, error);
goto out;
}
g_assert (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED ||
status == MM_BEARER_CONNECTION_STATUS_CONNECTED);
g_task_return_int (task, (gssize) status);
out:
g_object_unref (task);
}
static void
load_connection_status_by_cid (MMBroadbandBearerCinterion *bearer,
gint cid,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
g_autoptr(MMBaseModem) modem = NULL;
task = g_task_new (bearer, NULL, callback, user_data);
if (cid == MM_3GPP_PROFILE_ID_UNKNOWN) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Unknown profile id to check connection status");
g_object_unref (task);
return;
}
g_task_set_task_data (task, GUINT_TO_POINTER (cid), NULL);
g_object_get (bearer,
MM_BASE_BEARER_MODEM, &modem,
NULL);
mm_base_modem_at_command (modem,
"^SWWAN?",
5,
FALSE,
(GAsyncReadyCallback) swwan_check_status_ready,
task);
}
static void
load_connection_status (MMBaseBearer *bearer,
GAsyncReadyCallback callback,
gpointer user_data)
{
load_connection_status_by_cid (MM_BROADBAND_BEARER_CINTERION (bearer),
mm_base_bearer_get_profile_id (bearer),
callback,
user_data);
}
/******************************************************************************/
/* Dial 3GPP */
DIAL_3GPP_CONTEXT_STEP_FIRST = 0,
DIAL_3GPP_CONTEXT_STEP_AUTH,
DIAL_3GPP_CONTEXT_STEP_START_SWWAN,
DIAL_3GPP_CONTEXT_STEP_VALIDATE_CONNECTION,
DIAL_3GPP_CONTEXT_STEP_LAST,
} Dial3gppContextStep;
typedef struct {
MMBroadbandBearerCinterion *self;
MMBaseModem *modem;
MMPortSerialAt *primary;
MMPort *data;
gint usb_interface_config_index;
Dial3gppContextStep step;
} Dial3gppContext;
dial_3gpp_context_free (Dial3gppContext *ctx)
{
g_object_unref (ctx->modem);
g_object_unref (ctx->self);
g_object_unref (ctx->primary);
g_clear_object (&ctx->data);
g_slice_free (Dial3gppContext, ctx);
static MMPort *
dial_3gpp_finish (MMBroadbandBearer *self,
GAsyncResult *res,
GError **error)
return MM_PORT (g_task_propagate_pointer (G_TASK (res), error));
static void dial_3gpp_context_step (GTask *task);
dial_connection_status_ready (MMBroadbandBearerCinterion *self,
GAsyncResult *res,
GTask *task)
MMBearerConnectionStatus status;
Dial3gppContext *ctx;
GError *error = NULL;
ctx = (Dial3gppContext *) g_task_get_task_data (task);
status = load_connection_status_finish (MM_BASE_BEARER (self), res, &error);
if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"CID %u is reported disconnected", ctx->cid);
g_object_unref (task);
return;
}
g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED);
/* Go to next step */
ctx->step++;
dial_3gpp_context_step (task);
}
static void
common_dial_operation_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
Dial3gppContext *ctx;
GError *error = NULL;
ctx = (Dial3gppContext *) g_task_get_task_data (task);
if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
/* Go to next step */
ctx->step++;
dial_3gpp_context_step (task);
}
static void
handle_cancel_dial (GTask *task)
Dial3gppContext *ctx;
gchar *command;
ctx = (Dial3gppContext *) g_task_get_task_data (task);
/* Disconnect, may not succeed. Will not check response on cancel */
command = g_strdup_printf ("^SWWAN=0,%u,%u",
ctx->cid, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
mm_base_modem_at_command_full (ctx->modem,
ctx->primary,
command,
3,
FALSE,
FALSE,
NULL,
NULL,
NULL);
}
static void
dial_3gpp_context_step (GTask *task)
MMBroadbandBearerCinterion *self;
Dial3gppContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
/* Check for cancellation */
if (g_task_return_error_if_cancelled (task)) {
g_object_unref (task);
return;
}
case DIAL_3GPP_CONTEXT_STEP_FIRST:
/* fall through */
case DIAL_3GPP_CONTEXT_STEP_AUTH: {
gchar *command;
command = mm_cinterion_build_auth_string (self,
mm_broadband_modem_cinterion_get_family (MM_BROADBAND_MODEM_CINTERION (ctx->modem)),
mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)),
ctx->cid);
mm_obj_dbg (self, "dial step %u/%u: authenticating...", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST);
/* Send SGAUTH write, if User & Pass are provided.
* advance to next state by callback */
mm_base_modem_at_command_full (ctx->modem,
ctx->primary,
command,
10,
FALSE,
FALSE,
NULL,
(GAsyncReadyCallback) common_dial_operation_ready,
task);
g_free (command);
return;
}
mm_obj_dbg (self, "dial step %u/%u: authentication not required", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST);
} /* fall through */
case DIAL_3GPP_CONTEXT_STEP_START_SWWAN: {
mm_obj_dbg (self, "dial step %u/%u: starting SWWAN interface %u connection...",
ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
command = g_strdup_printf ("^SWWAN=1,%u,%u",
usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
mm_base_modem_at_command_full (ctx->modem,
ctx->primary,
command,
MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
FALSE,
FALSE,
NULL,
(GAsyncReadyCallback) common_dial_operation_ready,
task);
case DIAL_3GPP_CONTEXT_STEP_VALIDATE_CONNECTION:
mm_obj_dbg (self, "dial step %u/%u: checking SWWAN interface %u status...",
ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
load_connection_status_by_cid (ctx->self,
(gint) ctx->cid,
(GAsyncReadyCallback) dial_connection_status_ready,
case DIAL_3GPP_CONTEXT_STEP_LAST:
mm_obj_dbg (self, "dial step %u/%u: finished", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST);
g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
g_object_unref (task);
default:
g_assert_not_reached ();
}
}
static void
dial_3gpp (MMBroadbandBearer *self,
MMBaseModem *modem,
MMPortSerialAt *primary,
guint cid,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
GTask *task;
Dial3gppContext *ctx;
GError *error = NULL;
g_assert (primary != NULL);
/* Setup task and create connection context */
task = g_task_new (self, cancellable, callback, user_data);
ctx = g_slice_new0 (Dial3gppContext);
g_task_set_task_data (task, ctx, (GDestroyNotify) dial_3gpp_context_free);
/* Setup context */
ctx->self = MM_BROADBAND_BEARER_CINTERION (g_object_ref (self));
ctx->modem = g_object_ref (modem);
ctx->primary = g_object_ref (primary);
ctx->cid = cid;
ctx->step = DIAL_3GPP_CONTEXT_STEP_FIRST;
/* Get a net port to setup the connection on */
ctx->data = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET);
if (!ctx->data) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
"No valid data port found to launch connection");
g_object_unref (task);
/* Validate configuration */
ctx->usb_interface_config_index = get_usb_interface_config_index (ctx->data, &error);
if (ctx->usb_interface_config_index < 0) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
dial_3gpp_context_step (task);
}
/*****************************************************************************/
/* Disconnect 3GPP */
typedef enum {
DISCONNECT_3GPP_CONTEXT_STEP_FIRST,
DISCONNECT_3GPP_CONTEXT_STEP_STOP_SWWAN,
DISCONNECT_3GPP_CONTEXT_STEP_CONNECTION_STATUS,
DISCONNECT_3GPP_CONTEXT_STEP_LAST,
} Disconnect3gppContextStep;
typedef struct {
MMBroadbandBearerCinterion *self;
MMBaseModem *modem;
MMPortSerialAt *primary;
MMPort *data;
gint usb_interface_config_index;
Disconnect3gppContextStep step;
} Disconnect3gppContext;
static void
disconnect_3gpp_context_free (Disconnect3gppContext *ctx)
g_object_unref (ctx->data);
g_object_unref (ctx->primary);
g_object_unref (ctx->self);
g_object_unref (ctx->modem);
g_slice_free (Disconnect3gppContext, ctx);
}
static gboolean
disconnect_3gpp_finish (MMBroadbandBearer *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
static void disconnect_3gpp_context_step (GTask *task);
disconnect_connection_status_ready (MMBroadbandBearerCinterion *self,
GAsyncResult *res,
GTask *task)
MMBearerConnectionStatus status;
Disconnect3gppContext *ctx;
GError *error = NULL;
ctx = (Disconnect3gppContext *) g_task_get_task_data (task);
status = load_connection_status_finish (MM_BASE_BEARER (self), res, &error);
switch (status) {
case MM_BEARER_CONNECTION_STATUS_UNKNOWN:
/* Assume disconnected */
mm_obj_dbg (self, "couldn't get CID %u status, assume disconnected: %s", ctx->cid, error->message);
g_clear_error (&error);
break;
case MM_BEARER_CONNECTION_STATUS_DISCONNECTED:
break;
case MM_BEARER_CONNECTION_STATUS_CONNECTED:
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"CID %u is reported connected", ctx->cid);
g_object_unref (task);
case MM_BEARER_CONNECTION_STATUS_DISCONNECTING:
case MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED:
default:
g_assert_not_reached ();
/* Go on to next step */
ctx->step++;
disconnect_3gpp_context_step (task);
}
static void
swwan_disconnect_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
Disconnect3gppContext *ctx;
ctx = (Disconnect3gppContext *) g_task_get_task_data (task);
/* We don't bother to check error or response here since, ctx flow's
* next step checks it */
mm_base_modem_at_command_full_finish (modem, res, NULL);
/* Go on to next step */
ctx->step++;
disconnect_3gpp_context_step (task);
}
static void
disconnect_3gpp_context_step (GTask *task)
MMBroadbandBearerCinterion *self;
Disconnect3gppContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case DISCONNECT_3GPP_CONTEXT_STEP_FIRST:
ctx->step++;
/* fall through */
case DISCONNECT_3GPP_CONTEXT_STEP_STOP_SWWAN: {
gchar *command;
command = g_strdup_printf ("^SWWAN=0,%u,%u",
ctx->cid, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
mm_obj_dbg (self, "disconnect step %u/%u: disconnecting PDP CID %u...",
ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST, ctx->cid);
mm_base_modem_at_command_full (ctx->modem,
ctx->primary,
command,
MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
FALSE,
FALSE,
NULL,
(GAsyncReadyCallback) swwan_disconnect_ready,
task);
case DISCONNECT_3GPP_CONTEXT_STEP_CONNECTION_STATUS:
mm_obj_dbg (self, "disconnect step %u/%u: checking SWWAN interface %u status...",
ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST,
usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
load_connection_status_by_cid (MM_BROADBAND_BEARER_CINTERION (ctx->self),
(gint) ctx->cid,
(GAsyncReadyCallback) disconnect_connection_status_ready,
case DISCONNECT_3GPP_CONTEXT_STEP_LAST:
mm_obj_dbg (self, "disconnect step %u/%u: finished",
ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
default:
g_assert_not_reached ();
}
}
static void
disconnect_3gpp (MMBroadbandBearer *self,
MMBroadbandModem *modem,
MMPortSerialAt *primary,
MMPortSerialAt *secondary,
MMPort *data,
guint cid,
GAsyncReadyCallback callback,
Disconnect3gppContext *ctx;
GError *error = NULL;
g_assert (primary != NULL);
g_assert (data != NULL);
/* Setup task and create connection context */
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (Disconnect3gppContext);
g_task_set_task_data (task, ctx, (GDestroyNotify) disconnect_3gpp_context_free);
/* Setup context */
ctx->self = MM_BROADBAND_BEARER_CINTERION (g_object_ref (self));
ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
ctx->primary = g_object_ref (primary);
ctx->data = g_object_ref (data);
ctx->cid = cid;
ctx->step = DISCONNECT_3GPP_CONTEXT_STEP_FIRST;
/* Validate configuration */
ctx->usb_interface_config_index = get_usb_interface_config_index (data, &error);
if (ctx->usb_interface_config_index < 0) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Start */
disconnect_3gpp_context_step (task);
}
/*****************************************************************************/
/* Setup and Init Bearers */
MMBaseBearer *
mm_broadband_bearer_cinterion_new_finish (GAsyncResult *res,
GError **error)
{
GObject *bearer;
GObject *source;
source = g_async_result_get_source_object (res);
bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
g_object_unref (source);
if (!bearer)
return NULL;
/* Only export valid bearers */
mm_base_bearer_export (MM_BASE_BEARER (bearer));
return MM_BASE_BEARER (bearer);
}
void
mm_broadband_bearer_cinterion_new (MMBroadbandModemCinterion *modem,
MMBearerProperties *config,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_async_initable_new_async (
MM_TYPE_BROADBAND_BEARER_CINTERION,
G_PRIORITY_DEFAULT,
cancellable,
callback,
user_data,
MM_BASE_BEARER_MODEM, modem,
MM_BASE_BEARER_CONFIG, config,
NULL);
}
static void
mm_broadband_bearer_cinterion_init (MMBroadbandBearerCinterion *self)
{
}
static void
mm_broadband_bearer_cinterion_class_init (MMBroadbandBearerCinterionClass *klass)
{
MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
base_bearer_class->load_connection_status = load_connection_status;
base_bearer_class->load_connection_status_finish = load_connection_status_finish;
#if defined WITH_SYSTEMD_SUSPEND_RESUME
base_bearer_class->reload_connection_status = load_connection_status;
base_bearer_class->reload_connection_status_finish = load_connection_status_finish;
#endif
broadband_bearer_class->dial_3gpp = dial_3gpp;
broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish;
broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
}