Originally developed by: Ben Chan <benchan@chromium.org> This patch replaces mm_bearer_report_disconnection() with a more generic mm_bearer_report_connection_status(), which allows reporting any connection status of a bearer. This further allows getting rid of those custom report_connection_status functions in plugic specific bearer subclasses. Note that while plugin-specific implementations can receive multiple 'MMBearerConnectionStatus' values, the generic implementation is only allowed to receive DISCONNECTED. Plugins need to make sure that they process all the other status values, and only report DISCONNECTED to the parent when required. MBM: The MBM bearer implementation of report_connection_status() expects either CONNECTED or DISCONNECTED. If any of these is received and there is an ongoing connection attempt, the corresponding operation will be completed. If there is no connection attempt, we will just handle the DISCONNECTED state, calling the parent method to notify that the modem got network-disconnected. Icera: The Icera bearer implementation of report_connection_status() expects either CONNECTED, CONNECT FAILED or DISCONNECTED. If any of these is received and there is an ongoing connection or disconnection attempt, the corresponding operation will be completed. If there is no connection or disconnection attempt, we will just handle the CONNECT FAILED and DISCONNECTED states, calling the parent method (always with DISCONNECTED) to notify that the modem got network-disconnected. Option/HSO: The Option/HSO bearer implementation of report_connection_status() expects either CONNECTED, CONNECTION FAILED or DISCONNECTED. If any of these is received and there is an ongoing connection or disconnection attempt, the corresponding operation will be completed. If there is no connection or disconnection attempt, we will just handle the CONNECTION FAILED and DISCONNECTED states, calling the parent method (always with DISCONNECTED) to notify that the modem got network-disconnected. Huawei: The Huawei bearer implementation of report_connection_status() expects either CONNECTED or DISCONNECTED. These messages are not used to process pending connection or disconnection attempts; so if they are received while one of these is on-going, it will just be ignored. CONNECTED reports are also ignored, so we will just handle the DISCONNECTED state, calling the parent method to notify that the modem got network-disconnected. Altair-LTE: The Altair-LTE bearers will only report DISCONNECTED on network-disconnected cases. There is no custom report_connection_status(). Novatel-LTE: The Novatel-LTE bearers will only report DISCONNECTED on network-disconnected cases. There is no custom report_connection_status().
815 lines
30 KiB
C
815 lines
30 KiB
C
/* -*- 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 hso) 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) 2008 - 2009 Novell, Inc.
|
|
* Copyright (C) 2009 - 2012 Red Hat, Inc.
|
|
* Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
|
|
#include "ModemManager.h"
|
|
#include "mm-modem-helpers.h"
|
|
#include "mm-log.h"
|
|
#include "mm-errors-types.h"
|
|
#include "mm-iface-modem.h"
|
|
#include "mm-iface-modem-3gpp.h"
|
|
#include "mm-iface-modem-location.h"
|
|
#include "mm-base-modem-at.h"
|
|
#include "mm-broadband-modem-hso.h"
|
|
#include "mm-broadband-bearer-hso.h"
|
|
#include "mm-bearer-list.h"
|
|
|
|
static void iface_modem_init (MMIfaceModem *iface);
|
|
static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
|
|
static void iface_modem_location_init (MMIfaceModemLocation *iface);
|
|
|
|
static MMIfaceModem3gpp *iface_modem_3gpp_parent;
|
|
static MMIfaceModemLocation *iface_modem_location_parent;
|
|
|
|
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemHso, mm_broadband_modem_hso, MM_TYPE_BROADBAND_MODEM_OPTION, 0,
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init));
|
|
|
|
struct _MMBroadbandModemHsoPrivate {
|
|
/* Regex for connected notifications */
|
|
GRegex *_owancall_regex;
|
|
|
|
MMModemLocationSource enabled_sources;
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
/* Create Bearer (Modem interface) */
|
|
|
|
static MMBearer *
|
|
modem_create_bearer_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
MMBearer *bearer;
|
|
|
|
bearer = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
|
|
mm_dbg ("New %s bearer created at DBus path '%s'",
|
|
MM_IS_BROADBAND_BEARER_HSO (bearer) ? "HSO" : "Generic",
|
|
mm_bearer_get_path (bearer));
|
|
|
|
return g_object_ref (bearer);
|
|
}
|
|
|
|
static void
|
|
broadband_bearer_new_ready (GObject *source,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
MMBearer *bearer = NULL;
|
|
GError *error = NULL;
|
|
|
|
bearer = mm_broadband_bearer_new_finish (res, &error);
|
|
if (!bearer)
|
|
g_simple_async_result_take_error (simple, error);
|
|
else
|
|
g_simple_async_result_set_op_res_gpointer (simple,
|
|
bearer,
|
|
(GDestroyNotify)g_object_unref);
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
broadband_bearer_hso_new_ready (GObject *source,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
MMBearer *bearer = NULL;
|
|
GError *error = NULL;
|
|
|
|
bearer = mm_broadband_bearer_hso_new_finish (res, &error);
|
|
if (!bearer)
|
|
g_simple_async_result_take_error (simple, error);
|
|
else
|
|
g_simple_async_result_set_op_res_gpointer (simple,
|
|
bearer,
|
|
(GDestroyNotify)g_object_unref);
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
modem_create_bearer (MMIfaceModem *self,
|
|
MMBearerProperties *properties,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GSimpleAsyncResult *result;
|
|
|
|
result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
modem_create_bearer);
|
|
|
|
if (mm_bearer_properties_get_ip_type (properties) &
|
|
(MM_BEARER_IP_FAMILY_IPV6 | MM_BEARER_IP_FAMILY_IPV4V6)) {
|
|
mm_dbg ("Creating generic bearer (IPv6 requested)...");
|
|
mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
|
|
properties,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)broadband_bearer_new_ready,
|
|
result);
|
|
return;
|
|
}
|
|
|
|
mm_dbg ("Creating HSO bearer...");
|
|
mm_broadband_bearer_hso_new (MM_BROADBAND_MODEM_HSO (self),
|
|
properties,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)broadband_bearer_hso_new_ready,
|
|
result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load unlock retries (Modem interface) */
|
|
|
|
static MMUnlockRetries *
|
|
load_unlock_retries_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
|
return NULL;
|
|
return (MMUnlockRetries *) g_object_ref (g_simple_async_result_get_op_res_gpointer (
|
|
G_SIMPLE_ASYNC_RESULT (res)));
|
|
}
|
|
|
|
static void
|
|
load_unlock_retries_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *operation_result)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
int pin1, puk1;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (!response) {
|
|
mm_dbg ("Couldn't query unlock retries: '%s'", error->message);
|
|
g_simple_async_result_take_error (operation_result, error);
|
|
g_simple_async_result_complete (operation_result);
|
|
g_object_unref (operation_result);
|
|
return;
|
|
}
|
|
|
|
response = mm_strip_tag (response, "_OERCN:");
|
|
if (sscanf (response, " %d, %d", &pin1, &puk1) == 2) {
|
|
MMUnlockRetries *retries;
|
|
retries = mm_unlock_retries_new ();
|
|
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1);
|
|
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1);
|
|
g_simple_async_result_set_op_res_gpointer (operation_result,
|
|
retries,
|
|
(GDestroyNotify)g_object_unref);
|
|
} else {
|
|
g_simple_async_result_set_error (operation_result,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Invalid unlock retries response: '%s'",
|
|
response);
|
|
}
|
|
g_simple_async_result_complete (operation_result);
|
|
g_object_unref (operation_result);
|
|
}
|
|
|
|
static void
|
|
load_unlock_retries (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"_OERCN?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)load_unlock_retries_ready,
|
|
g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
load_unlock_retries));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup/Cleanup unsolicited events (3GPP interface) */
|
|
|
|
typedef struct {
|
|
guint cid;
|
|
MMBearerConnectionStatus status;
|
|
} BearerListReportStatusForeachContext;
|
|
|
|
static void
|
|
bearer_list_report_status_foreach (MMBearer *bearer,
|
|
BearerListReportStatusForeachContext *ctx)
|
|
{
|
|
if (mm_broadband_bearer_get_3gpp_cid (MM_BROADBAND_BEARER (bearer)) != ctx->cid)
|
|
return;
|
|
|
|
mm_bearer_report_connection_status (MM_BEARER (bearer), ctx->status);
|
|
}
|
|
|
|
static void
|
|
hso_connection_status_changed (MMAtSerialPort *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHso *self)
|
|
{
|
|
MMBearerList *list = NULL;
|
|
BearerListReportStatusForeachContext ctx;
|
|
guint cid;
|
|
guint status;
|
|
|
|
/* Ensure we got proper parsed values */
|
|
if (!mm_get_uint_from_match_info (match_info, 1, &cid) ||
|
|
!mm_get_uint_from_match_info (match_info, 2, &status))
|
|
return;
|
|
|
|
/* Setup context */
|
|
ctx.cid = cid;
|
|
ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN;
|
|
|
|
switch (status) {
|
|
case 1:
|
|
ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED;
|
|
break;
|
|
case 3:
|
|
ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED;
|
|
break;
|
|
case 0:
|
|
ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* If unknown status, don't try to report anything */
|
|
if (ctx.status == MM_BEARER_CONNECTION_STATUS_UNKNOWN)
|
|
return;
|
|
|
|
/* If empty bearer list, nothing else to do */
|
|
g_object_get (self,
|
|
MM_IFACE_MODEM_BEARER_LIST, &list,
|
|
NULL);
|
|
if (!list)
|
|
return;
|
|
|
|
/* Will report status only in the bearer with the specific CID */
|
|
mm_bearer_list_foreach (list,
|
|
(MMBearerListForeachFunc)bearer_list_report_status_foreach,
|
|
&ctx);
|
|
g_object_unref (list);
|
|
}
|
|
|
|
static gboolean
|
|
modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void
|
|
parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
|
|
g_simple_async_result_take_error (simple, error);
|
|
else {
|
|
/* Our own setup now */
|
|
mm_at_serial_port_add_unsolicited_msg_handler (
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
|
|
MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex,
|
|
(MMAtSerialUnsolicitedMsgFn)hso_connection_status_changed,
|
|
self,
|
|
NULL);
|
|
|
|
g_simple_async_result_set_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res), TRUE);
|
|
}
|
|
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GSimpleAsyncResult *result;
|
|
|
|
result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
modem_3gpp_setup_unsolicited_events);
|
|
|
|
/* Chain up parent's setup */
|
|
iface_modem_3gpp_parent->setup_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
|
|
result);
|
|
}
|
|
|
|
static void
|
|
parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
|
|
g_simple_async_result_take_error (simple, error);
|
|
else
|
|
g_simple_async_result_set_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res), TRUE);
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GSimpleAsyncResult *result;
|
|
|
|
result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
modem_3gpp_cleanup_unsolicited_events);
|
|
|
|
/* Our own cleanup first */
|
|
mm_at_serial_port_add_unsolicited_msg_handler (
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
|
|
MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex,
|
|
NULL, NULL, NULL);
|
|
|
|
/* And now chain up parent's cleanup */
|
|
iface_modem_3gpp_parent->cleanup_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
|
|
result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location capabilities loading (Location interface) */
|
|
|
|
static MMModemLocationSource
|
|
location_load_capabilities_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
|
return MM_MODEM_LOCATION_SOURCE_NONE;
|
|
|
|
return (MMModemLocationSource) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (
|
|
G_SIMPLE_ASYNC_RESULT (res)));
|
|
}
|
|
|
|
static void
|
|
parent_load_capabilities_ready (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
MMModemLocationSource sources;
|
|
GError *error = NULL;
|
|
|
|
sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error);
|
|
if (error) {
|
|
g_simple_async_result_take_error (simple, error);
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
return;
|
|
}
|
|
|
|
/* Now our own check.
|
|
*
|
|
* We could issue AT_OIFACE? to list the interfaces currently enabled in the
|
|
* module, to see if there is a 'GPS' interface enabled. But we'll just go
|
|
* and see if there is already a 'GPS control' AT port and a raw serial 'GPS'
|
|
* port grabbed.
|
|
*
|
|
* NOTE: A deeper implementation could handle the situation where the GPS
|
|
* interface is found disabled in AT_OIFACE?. In this case, we could issue
|
|
* AT_OIFACE="GPS",1 to enable it (and AT_OIFACE="GPS",0 to disable it), but
|
|
* enabling/disabling GPS involves a complete reboot of the modem, which is
|
|
* probably not the desired thing here.
|
|
*/
|
|
if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)) &&
|
|
mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)))
|
|
sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW);
|
|
|
|
/* So we're done, complete */
|
|
g_simple_async_result_set_op_res_gpointer (simple,
|
|
GUINT_TO_POINTER (sources),
|
|
NULL);
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
location_load_capabilities (MMIfaceModemLocation *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GSimpleAsyncResult *result;
|
|
|
|
result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
location_load_capabilities);
|
|
|
|
/* Chain up parent's setup */
|
|
iface_modem_location_parent->load_capabilities (
|
|
self,
|
|
(GAsyncReadyCallback)parent_load_capabilities_ready,
|
|
result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Disable location gathering (Location interface) */
|
|
|
|
static gboolean
|
|
disable_location_gathering_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void
|
|
gps_disabled_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
MMGpsSerialPort *gps_port;
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_at_command_full_finish (self, res, &error))
|
|
g_simple_async_result_take_error (simple, error);
|
|
else
|
|
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
|
|
|
|
/* Even if we get an error here, we try to close the GPS port */
|
|
gps_port = mm_base_modem_peek_port_gps (self);
|
|
if (gps_port)
|
|
mm_serial_port_close (MM_SERIAL_PORT (gps_port));
|
|
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
disable_location_gathering (MMIfaceModemLocation *self,
|
|
MMModemLocationSource source,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemHso *hso = MM_BROADBAND_MODEM_HSO (self);
|
|
GSimpleAsyncResult *result;
|
|
gboolean stop_gps = FALSE;
|
|
|
|
result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
disable_location_gathering);
|
|
|
|
/* Only stop GPS engine if no GPS-related sources enabled */
|
|
if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
|
|
hso->priv->enabled_sources &= ~source;
|
|
|
|
if (!(hso->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW)))
|
|
stop_gps = TRUE;
|
|
}
|
|
|
|
if (stop_gps) {
|
|
/* We enable continuous GPS fixes with AT_OGPS=0 */
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)),
|
|
"_OGPS=0",
|
|
3,
|
|
FALSE,
|
|
FALSE, /* raw */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)gps_disabled_ready,
|
|
result);
|
|
return;
|
|
}
|
|
|
|
/* For any other location (e.g. 3GPP), or if still some GPS needed, just return */
|
|
g_simple_async_result_set_op_res_gboolean (result, TRUE);
|
|
g_simple_async_result_complete_in_idle (result);
|
|
g_object_unref (result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enable location gathering (Location interface) */
|
|
|
|
typedef struct {
|
|
MMBroadbandModemHso *self;
|
|
GSimpleAsyncResult *result;
|
|
MMModemLocationSource source;
|
|
} EnableLocationGatheringContext;
|
|
|
|
static void
|
|
enable_location_gathering_context_complete_and_free (EnableLocationGatheringContext *ctx)
|
|
{
|
|
g_simple_async_result_complete (ctx->result);
|
|
g_object_unref (ctx->result);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static gboolean
|
|
enable_location_gathering_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void
|
|
gps_enabled_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
EnableLocationGatheringContext *ctx)
|
|
{
|
|
MMGpsSerialPort *gps_port;
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
enable_location_gathering_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
gps_port = mm_base_modem_peek_port_gps (self);
|
|
if (!gps_port ||
|
|
!mm_serial_port_open (MM_SERIAL_PORT (gps_port), &error)) {
|
|
if (error)
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
else
|
|
g_simple_async_result_set_error (ctx->result,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't open raw GPS serial port");
|
|
} else
|
|
g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
|
|
|
|
enable_location_gathering_context_complete_and_free (ctx);
|
|
}
|
|
|
|
static void
|
|
parent_enable_location_gathering_ready (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
EnableLocationGatheringContext *ctx)
|
|
{
|
|
gboolean start_gps = FALSE;
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_location_parent->enable_location_gathering_finish (self, res, &error)) {
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
enable_location_gathering_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Now our own enabling */
|
|
|
|
/* NMEA and RAW are both enabled in the same way */
|
|
if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
|
|
/* Only start GPS engine if not done already */
|
|
if (!(ctx->self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW)))
|
|
start_gps = TRUE;
|
|
ctx->self->priv->enabled_sources |= ctx->source;
|
|
}
|
|
|
|
if (start_gps) {
|
|
/* We enable continuous GPS fixes with AT_OGPS=2 */
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)),
|
|
"_OGPS=2",
|
|
3,
|
|
FALSE,
|
|
FALSE, /* raw */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)gps_enabled_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
|
|
/* For any other location (e.g. 3GPP), or if GPS already running just return */
|
|
g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
|
|
enable_location_gathering_context_complete_and_free (ctx);
|
|
}
|
|
|
|
static void
|
|
enable_location_gathering (MMIfaceModemLocation *self,
|
|
MMModemLocationSource source,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
EnableLocationGatheringContext *ctx;
|
|
|
|
ctx = g_new (EnableLocationGatheringContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
enable_location_gathering);
|
|
ctx->source = source;
|
|
|
|
/* Chain up parent's gathering enable */
|
|
iface_modem_location_parent->enable_location_gathering (
|
|
self,
|
|
source,
|
|
(GAsyncReadyCallback)parent_enable_location_gathering_ready,
|
|
ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup ports (Broadband modem class) */
|
|
|
|
static void
|
|
trace_received (MMGpsSerialPort *port,
|
|
const gchar *trace,
|
|
MMIfaceModemLocation *self)
|
|
{
|
|
/* Helper to debug GPS location related issues. Don't depend on a real GPS
|
|
* fix for debugging, just use some random values to update */
|
|
#if 0
|
|
if (g_str_has_prefix (trace, "$GPGGA")) {
|
|
GString *str;
|
|
GDateTime *now;
|
|
|
|
now = g_date_time_new_now_utc ();
|
|
str = g_string_new ("");
|
|
g_string_append_printf (str,
|
|
"$GPGGA,%02u%02u%02u,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47",
|
|
g_date_time_get_hour (now),
|
|
g_date_time_get_minute (now),
|
|
g_date_time_get_second (now));
|
|
mm_iface_modem_location_gps_update (self, str->str);
|
|
g_string_free (str, TRUE);
|
|
g_date_time_unref (now);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
mm_iface_modem_location_gps_update (self, trace);
|
|
}
|
|
|
|
static void
|
|
setup_ports (MMBroadbandModem *self)
|
|
{
|
|
MMAtSerialPort *gps_control_port;
|
|
MMGpsSerialPort *gps_data_port;
|
|
|
|
/* Call parent's setup ports first always */
|
|
MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_hso_parent_class)->setup_ports (self);
|
|
|
|
/* _OWANCALL unsolicited messages are only expected in the primary port. */
|
|
mm_at_serial_port_add_unsolicited_msg_handler (
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
|
|
MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex,
|
|
NULL, NULL, NULL);
|
|
|
|
g_object_set (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
|
|
MM_SERIAL_PORT_SEND_DELAY, (guint64) 0,
|
|
/* built-in echo removal conflicts with unsolicited _OWANCALL
|
|
* messages, which are not <CR><LF> prefixed. */
|
|
MM_AT_SERIAL_PORT_REMOVE_ECHO, FALSE,
|
|
NULL);
|
|
|
|
gps_control_port = mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self));
|
|
gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
|
|
if (gps_control_port && gps_data_port) {
|
|
/* It may happen that the modem was started with GPS already enabled, or
|
|
* maybe ModemManager got rebooted and it was left enabled before. We'll make
|
|
* sure that it is disabled when we initialize the modem */
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
gps_control_port,
|
|
"_OGPS=0",
|
|
3, FALSE, FALSE, NULL, NULL, NULL);
|
|
|
|
/* Add handler for the NMEA traces */
|
|
mm_gps_serial_port_add_trace_handler (gps_data_port,
|
|
(MMGpsSerialTraceFn)trace_received,
|
|
self,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
MMBroadbandModemHso *
|
|
mm_broadband_modem_hso_new (const gchar *device,
|
|
const gchar **drivers,
|
|
const gchar *plugin,
|
|
guint16 vendor_id,
|
|
guint16 product_id)
|
|
{
|
|
return g_object_new (MM_TYPE_BROADBAND_MODEM_HSO,
|
|
MM_BASE_MODEM_DEVICE, device,
|
|
MM_BASE_MODEM_DRIVERS, drivers,
|
|
MM_BASE_MODEM_PLUGIN, plugin,
|
|
MM_BASE_MODEM_VENDOR_ID, vendor_id,
|
|
MM_BASE_MODEM_PRODUCT_ID, product_id,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *object)
|
|
{
|
|
MMBroadbandModemHso *self = MM_BROADBAND_MODEM_HSO (object);
|
|
|
|
g_regex_unref (self->priv->_owancall_regex);
|
|
|
|
G_OBJECT_CLASS (mm_broadband_modem_hso_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
mm_broadband_modem_hso_init (MMBroadbandModemHso *self)
|
|
{
|
|
/* Initialize private data */
|
|
self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
|
|
MM_TYPE_BROADBAND_MODEM_HSO,
|
|
MMBroadbandModemHsoPrivate);
|
|
|
|
self->priv->_owancall_regex = g_regex_new ("_OWANCALL: (\\d),\\s*(\\d)\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE;
|
|
}
|
|
|
|
static void
|
|
iface_modem_init (MMIfaceModem *iface)
|
|
{
|
|
iface->create_bearer = modem_create_bearer;
|
|
iface->create_bearer_finish = modem_create_bearer_finish;
|
|
iface->load_unlock_retries = load_unlock_retries;
|
|
iface->load_unlock_retries_finish = load_unlock_retries_finish;
|
|
|
|
/* HSO modems don't need the extra 10s wait after powering up */
|
|
iface->modem_after_power_up = NULL;
|
|
iface->modem_after_power_up_finish = NULL;
|
|
}
|
|
|
|
static void
|
|
iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
|
|
{
|
|
iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
|
|
iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
|
|
iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
|
|
iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
|
|
}
|
|
|
|
static void
|
|
iface_modem_location_init (MMIfaceModemLocation *iface)
|
|
{
|
|
iface_modem_location_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->load_capabilities = location_load_capabilities;
|
|
iface->load_capabilities_finish = location_load_capabilities_finish;
|
|
iface->enable_location_gathering = enable_location_gathering;
|
|
iface->enable_location_gathering_finish = enable_location_gathering_finish;
|
|
iface->disable_location_gathering = disable_location_gathering;
|
|
iface->disable_location_gathering_finish = disable_location_gathering_finish;
|
|
}
|
|
|
|
static void
|
|
mm_broadband_modem_hso_class_init (MMBroadbandModemHsoClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
|
|
|
|
g_type_class_add_private (object_class, sizeof (MMBroadbandModemHsoPrivate));
|
|
|
|
object_class->finalize = finalize;
|
|
broadband_modem_class->setup_ports = setup_ports;
|
|
}
|