2150 lines
71 KiB
C
2150 lines
71 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 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) 2008 - 2009 Novell, Inc.
|
|
* Copyright (C) 2009 - 2010 Red Hat, Inc.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "mm-generic-cdma.h"
|
|
#include "mm-modem-cdma.h"
|
|
#include "mm-modem-simple.h"
|
|
#include "mm-at-serial-port.h"
|
|
#include "mm-qcdm-serial-port.h"
|
|
#include "mm-errors.h"
|
|
#include "mm-callback-info.h"
|
|
#include "mm-serial-parsers.h"
|
|
#include "mm-modem-helpers.h"
|
|
#include "libqcdm/src/commands.h"
|
|
|
|
#define MM_GENERIC_CDMA_PREV_STATE_TAG "prev-state"
|
|
|
|
static void simple_reg_callback (MMModemCdma *modem,
|
|
MMModemCdmaRegistrationState cdma_1x_reg_state,
|
|
MMModemCdmaRegistrationState evdo_reg_state,
|
|
GError *error,
|
|
gpointer user_data);
|
|
|
|
static void simple_state_machine (MMModem *modem, GError *error, gpointer user_data);
|
|
|
|
static void update_enabled_state (MMGenericCdma *self,
|
|
gboolean stay_connected,
|
|
MMModemStateReason reason);
|
|
|
|
static void modem_init (MMModem *modem_class);
|
|
static void modem_cdma_init (MMModemCdma *cdma_class);
|
|
static void modem_simple_init (MMModemSimple *class);
|
|
|
|
G_DEFINE_TYPE_EXTENDED (MMGenericCdma, mm_generic_cdma, MM_TYPE_MODEM_BASE, 0,
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_CDMA, modem_cdma_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_SIMPLE, modem_simple_init))
|
|
|
|
#define MM_GENERIC_CDMA_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_GENERIC_CDMA, MMGenericCdmaPrivate))
|
|
|
|
typedef struct {
|
|
guint32 cdma1x_quality;
|
|
guint32 evdo_quality;
|
|
gboolean valid;
|
|
gboolean evdo_rev0;
|
|
gboolean evdo_revA;
|
|
gboolean reg_try_css;
|
|
gboolean has_spservice;
|
|
gboolean has_speri;
|
|
|
|
guint poll_id;
|
|
|
|
MMModemCdmaRegistrationState cdma_1x_reg_state;
|
|
MMModemCdmaRegistrationState evdo_reg_state;
|
|
|
|
guint reg_tries;
|
|
guint reg_retry_id;
|
|
guint reg_state_changed_id;
|
|
MMCallbackInfo *simple_connect_info;
|
|
|
|
MMAtSerialPort *primary;
|
|
MMAtSerialPort *secondary;
|
|
MMQcdmSerialPort *qcdm;
|
|
MMPort *data;
|
|
} MMGenericCdmaPrivate;
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_EVDO_REV0,
|
|
PROP_EVDO_REVA,
|
|
PROP_REG_TRY_CSS,
|
|
LAST_PROP
|
|
};
|
|
|
|
MMModem *
|
|
mm_generic_cdma_new (const char *device,
|
|
const char *driver,
|
|
const char *plugin,
|
|
gboolean evdo_rev0,
|
|
gboolean evdo_revA)
|
|
{
|
|
g_return_val_if_fail (device != NULL, NULL);
|
|
g_return_val_if_fail (driver != NULL, NULL);
|
|
g_return_val_if_fail (plugin != NULL, NULL);
|
|
|
|
return MM_MODEM (g_object_new (MM_TYPE_GENERIC_CDMA,
|
|
MM_MODEM_MASTER_DEVICE, device,
|
|
MM_MODEM_DRIVER, driver,
|
|
MM_MODEM_PLUGIN, plugin,
|
|
MM_GENERIC_CDMA_EVDO_REV0, evdo_rev0,
|
|
MM_GENERIC_CDMA_EVDO_REVA, evdo_revA,
|
|
NULL));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
check_valid (MMGenericCdma *self)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
gboolean new_valid = FALSE;
|
|
|
|
if (priv->primary && priv->data)
|
|
new_valid = TRUE;
|
|
|
|
mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid);
|
|
}
|
|
|
|
static gboolean
|
|
owns_port (MMModem *modem, const char *subsys, const char *name)
|
|
{
|
|
return !!mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name);
|
|
}
|
|
|
|
MMPort *
|
|
mm_generic_cdma_grab_port (MMGenericCdma *self,
|
|
const char *subsys,
|
|
const char *name,
|
|
MMPortType suggested_type,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
MMPortType ptype = MM_PORT_TYPE_IGNORED;
|
|
MMPort *port;
|
|
|
|
g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), FALSE);
|
|
if (priv->primary)
|
|
g_return_val_if_fail (suggested_type != MM_PORT_TYPE_PRIMARY, FALSE);
|
|
|
|
if (!strcmp (subsys, "tty")) {
|
|
if (suggested_type != MM_PORT_TYPE_UNKNOWN)
|
|
ptype = suggested_type;
|
|
else {
|
|
if (!priv->primary)
|
|
ptype = MM_PORT_TYPE_PRIMARY;
|
|
else if (!priv->secondary)
|
|
ptype = MM_PORT_TYPE_SECONDARY;
|
|
}
|
|
}
|
|
|
|
port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype);
|
|
if (port && MM_IS_AT_SERIAL_PORT (port)) {
|
|
g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL);
|
|
mm_at_serial_port_set_response_parser (MM_AT_SERIAL_PORT (port),
|
|
mm_serial_parser_v1_e1_parse,
|
|
mm_serial_parser_v1_e1_new (),
|
|
mm_serial_parser_v1_e1_destroy);
|
|
|
|
if (ptype == MM_PORT_TYPE_PRIMARY) {
|
|
priv->primary = MM_AT_SERIAL_PORT (port);
|
|
if (!priv->data) {
|
|
priv->data = port;
|
|
g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE);
|
|
}
|
|
check_valid (self);
|
|
} else if (ptype == MM_PORT_TYPE_SECONDARY)
|
|
priv->secondary = MM_AT_SERIAL_PORT (port);
|
|
} else if (MM_IS_QCDM_SERIAL_PORT (port) && !priv->qcdm) {
|
|
priv->qcdm = MM_QCDM_SERIAL_PORT (port);
|
|
} else {
|
|
/* Net device (if any) is the preferred data port */
|
|
if (!priv->data || MM_IS_AT_SERIAL_PORT (priv->data)) {
|
|
priv->data = port;
|
|
g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE);
|
|
check_valid (self);
|
|
}
|
|
}
|
|
|
|
return port;
|
|
}
|
|
|
|
static gboolean
|
|
grab_port (MMModem *modem,
|
|
const char *subsys,
|
|
const char *name,
|
|
MMPortType suggested_type,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
return !!mm_generic_cdma_grab_port (MM_GENERIC_CDMA (modem), subsys, name, suggested_type, user_data, error);
|
|
}
|
|
|
|
static void
|
|
release_port (MMModem *modem, const char *subsys, const char *name)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
|
|
MMPort *port;
|
|
|
|
port = mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name);
|
|
if (!port)
|
|
return;
|
|
|
|
if (port == (MMPort *) priv->primary) {
|
|
mm_modem_base_remove_port (MM_MODEM_BASE (modem), port);
|
|
priv->primary = NULL;
|
|
}
|
|
|
|
if (port == priv->data) {
|
|
priv->data = NULL;
|
|
g_object_notify (G_OBJECT (modem), MM_MODEM_DATA_DEVICE);
|
|
}
|
|
|
|
if (port == (MMPort *) priv->secondary) {
|
|
mm_modem_base_remove_port (MM_MODEM_BASE (modem), port);
|
|
priv->secondary = NULL;
|
|
}
|
|
|
|
if (port == (MMPort *) priv->qcdm) {
|
|
mm_modem_base_remove_port (MM_MODEM_BASE (modem), port);
|
|
priv->qcdm = NULL;
|
|
}
|
|
|
|
check_valid (MM_GENERIC_CDMA (modem));
|
|
}
|
|
|
|
MMAtSerialPort *
|
|
mm_generic_cdma_get_at_port (MMGenericCdma *modem,
|
|
MMPortType ptype)
|
|
{
|
|
g_return_val_if_fail (MM_IS_GENERIC_CDMA (modem), NULL);
|
|
g_return_val_if_fail (ptype != MM_PORT_TYPE_UNKNOWN, NULL);
|
|
|
|
if (ptype == MM_PORT_TYPE_PRIMARY)
|
|
return MM_GENERIC_CDMA_GET_PRIVATE (modem)->primary;
|
|
else if (ptype == MM_PORT_TYPE_SECONDARY)
|
|
return MM_GENERIC_CDMA_GET_PRIVATE (modem)->secondary;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
MMAtSerialPort *
|
|
mm_generic_cdma_get_best_at_port (MMGenericCdma *self, GError **error)
|
|
{
|
|
MMGenericCdmaPrivate *priv;
|
|
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), NULL);
|
|
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
if (!mm_port_get_connected (MM_PORT (priv->primary)))
|
|
return priv->primary;
|
|
|
|
if (!priv->secondary) {
|
|
g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED,
|
|
"Cannot perform this operation while connected");
|
|
}
|
|
|
|
return priv->secondary;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
mm_generic_cdma_set_1x_registration_state (MMGenericCdma *self,
|
|
MMModemCdmaRegistrationState new_state)
|
|
{
|
|
MMGenericCdmaPrivate *priv;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (MM_IS_GENERIC_CDMA (self));
|
|
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
if (priv->cdma_1x_reg_state != new_state) {
|
|
priv->cdma_1x_reg_state = new_state;
|
|
|
|
update_enabled_state (self, TRUE, MM_MODEM_STATE_REASON_NONE);
|
|
mm_modem_cdma_emit_registration_state_changed (MM_MODEM_CDMA (self),
|
|
priv->cdma_1x_reg_state,
|
|
priv->evdo_reg_state);
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_generic_cdma_set_evdo_registration_state (MMGenericCdma *self,
|
|
MMModemCdmaRegistrationState new_state)
|
|
{
|
|
MMGenericCdmaPrivate *priv;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (MM_IS_GENERIC_CDMA (self));
|
|
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
if (priv->evdo_reg_state == new_state)
|
|
return;
|
|
|
|
/* Don't update EVDO state if the card doesn't support it */
|
|
if ( priv->evdo_rev0
|
|
|| priv->evdo_revA
|
|
|| (new_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)) {
|
|
priv->evdo_reg_state = new_state;
|
|
|
|
update_enabled_state (self, TRUE, MM_MODEM_STATE_REASON_NONE);
|
|
mm_modem_cdma_emit_registration_state_changed (MM_MODEM_CDMA (self),
|
|
priv->cdma_1x_reg_state,
|
|
priv->evdo_reg_state);
|
|
}
|
|
}
|
|
|
|
MMModemCdmaRegistrationState
|
|
mm_generic_cdma_1x_get_registration_state_sync (MMGenericCdma *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
|
|
return MM_GENERIC_CDMA_GET_PRIVATE (self)->cdma_1x_reg_state;
|
|
}
|
|
|
|
MMModemCdmaRegistrationState
|
|
mm_generic_cdma_evdo_get_registration_state_sync (MMGenericCdma *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
|
|
return MM_GENERIC_CDMA_GET_PRIVATE (self)->evdo_reg_state;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
periodic_poll_reg_cb (MMModemCdma *modem,
|
|
MMModemCdmaRegistrationState cdma_1x_reg_state,
|
|
MMModemCdmaRegistrationState evdo_reg_state,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
/* cached reg state already updated */
|
|
}
|
|
|
|
static void
|
|
periodic_poll_signal_quality_cb (MMModem *modem,
|
|
guint32 result,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
/* cached signal quality already updated */
|
|
}
|
|
|
|
static gboolean
|
|
periodic_poll_cb (gpointer user_data)
|
|
{
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (user_data);
|
|
|
|
mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (self), periodic_poll_reg_cb, NULL);
|
|
mm_modem_cdma_get_signal_quality (MM_MODEM_CDMA (self), periodic_poll_signal_quality_cb, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
update_enabled_state (MMGenericCdma *self,
|
|
gboolean stay_connected,
|
|
MMModemStateReason reason)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
/* While connected we don't want registration status changes to change
|
|
* the modem's state away from CONNECTED.
|
|
*/
|
|
if (stay_connected && (mm_modem_get_state (MM_MODEM (self)) >= MM_MODEM_STATE_DISCONNECTING))
|
|
return;
|
|
|
|
if ( priv->cdma_1x_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN
|
|
|| priv->evdo_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
|
|
mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_REGISTERED, reason);
|
|
else
|
|
mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_ENABLED, reason);
|
|
}
|
|
|
|
static void
|
|
registration_cleanup (MMGenericCdma *self, GQuark error_class, guint32 error_num)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
GError *error = NULL;
|
|
|
|
priv->reg_tries = 0;
|
|
|
|
if (priv->reg_state_changed_id) {
|
|
g_signal_handler_disconnect (self, priv->reg_state_changed_id);
|
|
priv->reg_state_changed_id = 0;
|
|
}
|
|
|
|
if (priv->reg_retry_id) {
|
|
g_source_remove (priv->reg_retry_id);
|
|
priv->reg_retry_id = 0;
|
|
}
|
|
|
|
/* Return an error to any explicit callers of simple_connect */
|
|
if (priv->simple_connect_info && error_class) {
|
|
error = g_error_new_literal (error_class, error_num,
|
|
"Connection attempt terminated");
|
|
simple_state_machine (MM_MODEM (self), error, priv->simple_connect_info);
|
|
g_error_free (error);
|
|
}
|
|
priv->simple_connect_info = NULL;
|
|
}
|
|
|
|
static void
|
|
get_enable_info_done (MMModem *modem,
|
|
const char *manufacturer,
|
|
const char *model,
|
|
const char *version,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
/* Modem base class handles the response for us */
|
|
}
|
|
|
|
static void
|
|
spservice_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
if (!error) {
|
|
MM_GENERIC_CDMA_GET_PRIVATE (user_data)->has_spservice = TRUE;
|
|
|
|
/* +SPSERVICE provides a better indicator of registration status than
|
|
* +CSS, which some devices implement inconsistently.
|
|
*/
|
|
MM_GENERIC_CDMA_GET_PRIVATE (user_data)->reg_try_css = FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
speri_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
if (!error)
|
|
MM_GENERIC_CDMA_GET_PRIVATE (user_data)->has_speri = TRUE;
|
|
}
|
|
|
|
static void
|
|
enable_all_done (MMModem *modem, GError *error, gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = user_data;
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
if (error)
|
|
info->error = g_error_copy (error);
|
|
else {
|
|
/* Try to enable XON/XOFF flow control */
|
|
mm_at_serial_port_queue_command (priv->primary, "+IFC=1,1", 3, NULL, NULL);
|
|
|
|
/* Open up the second port, if one exists */
|
|
if (priv->secondary) {
|
|
if (!mm_serial_port_open (MM_SERIAL_PORT (priv->secondary), &info->error)) {
|
|
g_assert (info->error);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Open up the second port, if one exists */
|
|
if (priv->qcdm) {
|
|
if (!mm_serial_port_open (MM_SERIAL_PORT (priv->qcdm), &info->error)) {
|
|
g_assert (info->error);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE);
|
|
|
|
/* Grab device info right away */
|
|
mm_modem_get_info (modem, get_enable_info_done, NULL);
|
|
|
|
/* Check for support of Sprint-specific phone commands */
|
|
mm_at_serial_port_queue_command (priv->primary, "+SPSERVICE?", 3, spservice_done, self);
|
|
mm_at_serial_port_queue_command (priv->primary, "$SPERI?", 3, speri_done, self);
|
|
}
|
|
|
|
out:
|
|
if (info->error) {
|
|
mm_modem_set_state (MM_MODEM (info->modem),
|
|
MM_MODEM_STATE_DISABLED,
|
|
MM_MODEM_STATE_REASON_NONE);
|
|
}
|
|
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
enable_error_reporting_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
|
|
|
|
/* Just ignore errors, see comment in init_done() */
|
|
if (error)
|
|
g_warning ("Your CDMA modem does not support +CMEE command");
|
|
|
|
if (MM_GENERIC_CDMA_GET_CLASS (self)->post_enable)
|
|
MM_GENERIC_CDMA_GET_CLASS (self)->post_enable (self, enable_all_done, info);
|
|
else
|
|
enable_all_done (MM_MODEM (self), NULL, info);
|
|
}
|
|
|
|
static void
|
|
init_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
if (error) {
|
|
mm_modem_set_state (MM_MODEM (info->modem),
|
|
MM_MODEM_STATE_DISABLED,
|
|
MM_MODEM_STATE_REASON_NONE);
|
|
|
|
info->error = g_error_copy (error);
|
|
mm_callback_info_schedule (info);
|
|
} else {
|
|
/* Try to enable better error reporting. My experience so far indicates
|
|
there's some CDMA modems that does not support that.
|
|
FIXME: It's mandatory by spec, so it really shouldn't be optional. Figure
|
|
out which CDMA modems have problems with it and implement plugin for them.
|
|
*/
|
|
mm_at_serial_port_queue_command (port, "+CMEE=1", 3, enable_error_reporting_done, user_data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
flash_done (MMSerialPort *port, GError *error, gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
if (error) {
|
|
mm_modem_set_state (MM_MODEM (info->modem),
|
|
MM_MODEM_STATE_DISABLED,
|
|
MM_MODEM_STATE_REASON_NONE);
|
|
|
|
/* Flash failed for some reason */
|
|
info->error = g_error_copy (error);
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
|
|
mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "Z E0 V1 X4 &C1", 3, init_done, user_data);
|
|
}
|
|
|
|
static void
|
|
enable (MMModem *modem,
|
|
MMModemFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (modem);
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
MMCallbackInfo *info;
|
|
|
|
info = mm_callback_info_new (modem, callback, user_data);
|
|
|
|
if (!mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &info->error)) {
|
|
g_assert (info->error);
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
|
|
mm_modem_set_state (MM_MODEM (info->modem),
|
|
MM_MODEM_STATE_ENABLING,
|
|
MM_MODEM_STATE_REASON_NONE);
|
|
|
|
mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 100, flash_done, info);
|
|
}
|
|
|
|
static void
|
|
disable_set_previous_state (MMModem *modem, MMCallbackInfo *info)
|
|
{
|
|
MMModemState prev_state;
|
|
|
|
/* Reset old state since the operation failed */
|
|
prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_CDMA_PREV_STATE_TAG));
|
|
mm_modem_set_state (modem, prev_state, MM_MODEM_STATE_REASON_NONE);
|
|
}
|
|
|
|
static void
|
|
disable_all_done (MMModem *modem, GError *error, gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = user_data;
|
|
|
|
info->error = mm_modem_check_removed (modem, error);
|
|
if (info->error) {
|
|
if (modem)
|
|
disable_set_previous_state (modem, info);
|
|
} else {
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
mm_serial_port_close (MM_SERIAL_PORT (priv->primary));
|
|
mm_modem_set_state (modem, MM_MODEM_STATE_DISABLED, MM_MODEM_STATE_REASON_NONE);
|
|
|
|
priv->cdma_1x_reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
|
|
priv->evdo_reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
|
|
}
|
|
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
disable_flash_done (MMSerialPort *port,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = user_data;
|
|
MMGenericCdma *self;
|
|
|
|
info->error = mm_modem_check_removed (info->modem, error);
|
|
if (info->error) {
|
|
if (info->modem)
|
|
disable_set_previous_state (info->modem, info);
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
|
|
self = MM_GENERIC_CDMA (info->modem);
|
|
|
|
if (MM_GENERIC_CDMA_GET_CLASS (self)->post_disable)
|
|
MM_GENERIC_CDMA_GET_CLASS (self)->post_disable (self, disable_all_done, info);
|
|
else
|
|
disable_all_done (MM_MODEM (self), NULL, info);
|
|
}
|
|
|
|
static void
|
|
disable (MMModem *modem,
|
|
MMModemFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (modem);
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
MMCallbackInfo *info;
|
|
MMModemState state;
|
|
|
|
/* Tear down any ongoing registration */
|
|
registration_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL);
|
|
|
|
info = mm_callback_info_new (modem, callback, user_data);
|
|
|
|
/* Cache the previous state so we can reset it if the operation fails */
|
|
state = mm_modem_get_state (modem);
|
|
mm_callback_info_set_data (info,
|
|
MM_GENERIC_CDMA_PREV_STATE_TAG,
|
|
GUINT_TO_POINTER (state),
|
|
NULL);
|
|
|
|
/* Close auxiliary serial ports */
|
|
if (priv->secondary)
|
|
mm_serial_port_close (MM_SERIAL_PORT (priv->secondary));
|
|
if (priv->qcdm)
|
|
mm_serial_port_close (MM_SERIAL_PORT (priv->qcdm));
|
|
|
|
mm_modem_set_state (MM_MODEM (info->modem),
|
|
MM_MODEM_STATE_DISABLING,
|
|
MM_MODEM_STATE_REASON_NONE);
|
|
|
|
if (mm_port_get_connected (MM_PORT (priv->primary)))
|
|
mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, disable_flash_done, info);
|
|
else
|
|
disable_flash_done (MM_SERIAL_PORT (priv->primary), NULL, info);
|
|
}
|
|
|
|
static void
|
|
dial_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
info->error = mm_modem_check_removed (info->modem, error);
|
|
if (info->error) {
|
|
if (info->modem)
|
|
update_enabled_state (MM_GENERIC_CDMA (info->modem), FALSE, MM_MODEM_STATE_REASON_NONE);
|
|
} else {
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
/* Clear reg tries; we're obviously registered by this point */
|
|
registration_cleanup (self, 0, 0);
|
|
|
|
mm_port_set_connected (priv->data, TRUE);
|
|
mm_modem_set_state (info->modem, MM_MODEM_STATE_CONNECTED, MM_MODEM_STATE_REASON_NONE);
|
|
}
|
|
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
connect (MMModem *modem,
|
|
const char *number,
|
|
MMModemFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
|
|
MMCallbackInfo *info;
|
|
char *command;
|
|
|
|
mm_modem_set_state (modem, MM_MODEM_STATE_CONNECTING, MM_MODEM_STATE_REASON_NONE);
|
|
|
|
info = mm_callback_info_new (modem, callback, user_data);
|
|
command = g_strconcat ("DT", number, NULL);
|
|
mm_at_serial_port_queue_command (priv->primary, command, 90, dial_done, info);
|
|
g_free (command);
|
|
}
|
|
|
|
static void
|
|
disconnect_flash_done (MMSerialPort *port,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
MMModemState prev_state;
|
|
|
|
info->error = mm_modem_check_removed (info->modem, error);
|
|
if (info->error) {
|
|
if (info->modem) {
|
|
/* Reset old state since the operation failed */
|
|
prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_CDMA_PREV_STATE_TAG));
|
|
mm_modem_set_state (MM_MODEM (info->modem),
|
|
prev_state,
|
|
MM_MODEM_STATE_REASON_NONE);
|
|
}
|
|
} else {
|
|
mm_port_set_connected (MM_GENERIC_CDMA_GET_PRIVATE (info->modem)->data, FALSE);
|
|
update_enabled_state (MM_GENERIC_CDMA (info->modem), FALSE, MM_MODEM_STATE_REASON_NONE);
|
|
}
|
|
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
disconnect (MMModem *modem,
|
|
MMModemFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
|
|
MMCallbackInfo *info;
|
|
MMModemState state;
|
|
|
|
g_return_if_fail (priv->primary != NULL);
|
|
|
|
info = mm_callback_info_new (modem, callback, user_data);
|
|
|
|
/* Cache the previous state so we can reset it if the operation fails */
|
|
state = mm_modem_get_state (modem);
|
|
mm_callback_info_set_data (info,
|
|
MM_GENERIC_CDMA_PREV_STATE_TAG,
|
|
GUINT_TO_POINTER (state),
|
|
NULL);
|
|
|
|
mm_modem_set_state (modem, MM_MODEM_STATE_DISCONNECTING, MM_MODEM_STATE_REASON_NONE);
|
|
mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, disconnect_flash_done, info);
|
|
}
|
|
|
|
static void
|
|
get_card_info (MMModem *modem,
|
|
MMModemInfoFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMAtSerialPort *port;
|
|
GError *error = NULL;
|
|
|
|
port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &error);
|
|
mm_modem_base_get_card_info (MM_MODEM_BASE (modem), port, error, callback, user_data);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
mm_generic_cdma_update_cdma1x_quality (MMGenericCdma *self, guint32 quality)
|
|
{
|
|
MMGenericCdmaPrivate *priv;
|
|
|
|
g_return_if_fail (MM_IS_GENERIC_CDMA (self));
|
|
g_return_if_fail (quality >= 0 && quality <= 100);
|
|
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
if (priv->cdma1x_quality != quality) {
|
|
priv->cdma1x_quality = quality;
|
|
mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (self), quality);
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality)
|
|
{
|
|
MMGenericCdmaPrivate *priv;
|
|
|
|
g_return_if_fail (MM_IS_GENERIC_CDMA (self));
|
|
g_return_if_fail (quality >= 0 && quality <= 100);
|
|
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
if (priv->evdo_quality != quality) {
|
|
priv->evdo_quality = quality;
|
|
// FIXME: emit a signal
|
|
}
|
|
}
|
|
|
|
#define CSQ2_TRIED "csq?-tried"
|
|
|
|
static void
|
|
get_signal_quality_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMGenericCdmaPrivate *priv;
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
char *reply = response->str;
|
|
|
|
if (error) {
|
|
if (mm_callback_info_get_data (info, CSQ2_TRIED))
|
|
info->error = g_error_copy (error);
|
|
else {
|
|
/* Some modems want +CSQ, others want +CSQ?, and some of both types
|
|
* will return ERROR if they don't get the command they want. So
|
|
* try the other command if the first one fails.
|
|
*/
|
|
mm_callback_info_set_data (info, CSQ2_TRIED, GUINT_TO_POINTER (1), NULL);
|
|
mm_at_serial_port_queue_command (port, "+CSQ?", 3, get_signal_quality_done, info);
|
|
return;
|
|
}
|
|
} else {
|
|
int quality, ber;
|
|
|
|
/* Got valid reply */
|
|
if (!strncmp (reply, "+CSQ: ", 6))
|
|
reply += 6;
|
|
|
|
if (sscanf (reply, "%d, %d", &quality, &ber)) {
|
|
/* 99 means unknown/no service */
|
|
if (quality == 99) {
|
|
info->error = g_error_new_literal (MM_MOBILE_ERROR,
|
|
MM_MOBILE_ERROR_NO_NETWORK,
|
|
"No service");
|
|
} else {
|
|
/* Normalize the quality */
|
|
quality = CLAMP (quality, 0, 31) * 100 / 31;
|
|
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
|
|
mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL);
|
|
if (priv->cdma1x_quality != quality) {
|
|
priv->cdma1x_quality = quality;
|
|
mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (info->modem), quality);
|
|
}
|
|
}
|
|
} else
|
|
info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
|
|
"%s", "Could not parse signal quality results");
|
|
}
|
|
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
qcdm_pilot_sets_cb (MMQcdmSerialPort *port,
|
|
GByteArray *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = user_data;
|
|
MMGenericCdmaPrivate *priv;
|
|
QCDMResult *result;
|
|
guint32 num = 0, quality = 0, i;
|
|
float best_db = -28;
|
|
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
goto done;
|
|
}
|
|
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
|
|
|
|
/* Parse the response */
|
|
result = qcdm_cmd_pilot_sets_result ((const char *) response->data, response->len, &info->error);
|
|
if (!result)
|
|
goto done;
|
|
|
|
qcdm_cmd_pilot_sets_result_get_num (result, QCDM_CMD_PILOT_SETS_TYPE_ACTIVE, &num);
|
|
for (i = 0; i < num; i++) {
|
|
guint32 pn_offset = 0, ecio = 0;
|
|
float db = 0;
|
|
|
|
qcdm_cmd_pilot_sets_result_get_pilot (result,
|
|
QCDM_CMD_PILOT_SETS_TYPE_ACTIVE,
|
|
i,
|
|
&pn_offset,
|
|
&ecio,
|
|
&db);
|
|
best_db = MAX (db, best_db);
|
|
}
|
|
qcdm_result_unref (result);
|
|
|
|
if (num > 0) {
|
|
#define BEST_ECIO 3
|
|
#define WORST_ECIO 25
|
|
|
|
/* EC/IO dB ranges from roughly 0 to -31 dB. Lower == worse. We
|
|
* really only care about -3 to -25 dB though, since that's about what
|
|
* you'll see in real-world usage.
|
|
*/
|
|
best_db = CLAMP (ABS (best_db), BEST_ECIO, WORST_ECIO) - BEST_ECIO;
|
|
quality = (guint32) (100 - (best_db * 100 / (WORST_ECIO - BEST_ECIO)));
|
|
}
|
|
|
|
mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL);
|
|
|
|
if (priv->cdma1x_quality != quality) {
|
|
priv->cdma1x_quality = quality;
|
|
mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (info->modem), quality);
|
|
}
|
|
|
|
done:
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
get_signal_quality (MMModemCdma *modem,
|
|
MMModemUIntFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
|
|
MMCallbackInfo *info;
|
|
MMAtSerialPort *at_port;
|
|
|
|
info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data);
|
|
|
|
at_port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error);
|
|
if (!at_port && !priv->qcdm) {
|
|
g_message ("Returning saved signal quality %d", priv->cdma1x_quality);
|
|
mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->cdma1x_quality), NULL);
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
g_clear_error (&info->error);
|
|
|
|
if (at_port)
|
|
mm_at_serial_port_queue_command (at_port, "+CSQ", 3, get_signal_quality_done, info);
|
|
else if (priv->qcdm) {
|
|
GByteArray *pilot_sets;
|
|
|
|
/* Use CDMA1x pilot EC/IO if we can */
|
|
pilot_sets = g_byte_array_sized_new (25);
|
|
pilot_sets->len = qcdm_cmd_pilot_sets_new ((char *) pilot_sets->data, 25, NULL);
|
|
g_assert (pilot_sets->len);
|
|
mm_qcdm_serial_port_queue_command (priv->qcdm, pilot_sets, 3, qcdm_pilot_sets_cb, info);
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_string_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
const char *p;
|
|
|
|
if (error)
|
|
info->error = g_error_copy (error);
|
|
else {
|
|
p = mm_strip_tag (response->str, "+GSN:");
|
|
mm_callback_info_set_result (info, g_strdup (p), g_free);
|
|
}
|
|
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
get_esn (MMModemCdma *modem,
|
|
MMModemStringFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info;
|
|
MMAtSerialPort *port;
|
|
|
|
info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
|
|
|
|
port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error);
|
|
if (!port) {
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
|
|
mm_at_serial_port_queue_command_cached (port, "+GSN", 3, get_string_done, info);
|
|
}
|
|
|
|
static void
|
|
serving_system_invoke (MMCallbackInfo *info)
|
|
{
|
|
MMModemCdmaServingSystemFn callback = (MMModemCdmaServingSystemFn) info->callback;
|
|
|
|
callback (MM_MODEM_CDMA (info->modem),
|
|
GPOINTER_TO_UINT (mm_callback_info_get_data (info, "class")),
|
|
(unsigned char) GPOINTER_TO_UINT (mm_callback_info_get_data (info, "band")),
|
|
GPOINTER_TO_UINT (mm_callback_info_get_data (info, "sid")),
|
|
info->error,
|
|
info->user_data);
|
|
}
|
|
|
|
static int
|
|
normalize_class (const char *orig_class)
|
|
{
|
|
char class;
|
|
|
|
g_return_val_if_fail (orig_class != NULL, '0');
|
|
|
|
class = toupper (orig_class[0]);
|
|
|
|
/* Cellular (850MHz) */
|
|
if (class == '1' || class == 'C')
|
|
return 1;
|
|
/* PCS (1900MHz) */
|
|
if (class == '2' || class == 'P')
|
|
return 2;
|
|
|
|
/* Unknown/not registered */
|
|
return 0;
|
|
}
|
|
|
|
static char
|
|
normalize_band (const char *long_band, int *out_class)
|
|
{
|
|
char band;
|
|
|
|
g_return_val_if_fail (long_band != NULL, 'Z');
|
|
|
|
/* There are two response formats for the band; one includes the band
|
|
* class and the other doesn't. For modems that include the band class
|
|
* (ex Novatel S720) you'll see "Px" or "Cx" depending on whether the modem
|
|
* is registered on a PCS/1900 (P) or Cellular/850 (C) system.
|
|
*/
|
|
band = toupper (long_band[0]);
|
|
|
|
/* Possible band class in first position; return it */
|
|
if (band == 'C' || band == 'P') {
|
|
char tmp[2] = { band, '\0' };
|
|
|
|
*out_class = normalize_class (tmp);
|
|
band = toupper (long_band[1]);
|
|
}
|
|
|
|
/* normalize to A - F, and Z */
|
|
if (band >= 'A' && band <= 'F')
|
|
return band;
|
|
|
|
/* Unknown/not registered */
|
|
return 'Z';
|
|
}
|
|
|
|
static int
|
|
convert_sid (const char *sid)
|
|
{
|
|
long int tmp_sid;
|
|
|
|
g_return_val_if_fail (sid != NULL, 99999);
|
|
|
|
errno = 0;
|
|
tmp_sid = strtol (sid, NULL, 10);
|
|
if ((errno == EINVAL) || (errno == ERANGE))
|
|
return 99999;
|
|
else if (tmp_sid < G_MININT || tmp_sid > G_MAXINT)
|
|
return 99999;
|
|
|
|
return (int) tmp_sid;
|
|
}
|
|
|
|
static GError *
|
|
new_css_no_service_error (void)
|
|
{
|
|
/* NOTE: update reg_state_css_response() if this error changes */
|
|
return g_error_new_literal (MM_MOBILE_ERROR,
|
|
MM_MOBILE_ERROR_NO_NETWORK,
|
|
"No service");
|
|
}
|
|
|
|
static void
|
|
serving_system_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
char *reply = response->str;
|
|
int class = 0, sid = 99999, num;
|
|
unsigned char band = 'Z';
|
|
gboolean success = FALSE;
|
|
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
goto out;
|
|
}
|
|
|
|
if (strstr (reply, "+CSS: "))
|
|
reply += 6;
|
|
|
|
num = sscanf (reply, "? , %d", &sid);
|
|
if (num == 1) {
|
|
/* UTStarcom and Huawei modems that use IS-707-A format; note that
|
|
* this format obviously doesn't have other indicators like band and
|
|
* class and thus SID 0 will be reported as "no service" (see below).
|
|
*/
|
|
class = 0;
|
|
band = 'Z';
|
|
success = TRUE;
|
|
} else {
|
|
GRegex *r;
|
|
GMatchInfo *match_info;
|
|
int override_class = 0;
|
|
|
|
/* Format is "<band_class>,<band>,<sid>" */
|
|
r = g_regex_new ("\\s*([^,]*?)\\s*,\\s*([^,]*?)\\s*,\\s*(\\d+)", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
if (!r) {
|
|
info->error = g_error_new_literal (MM_MODEM_ERROR,
|
|
MM_MODEM_ERROR_GENERAL,
|
|
"Could not parse Serving System results (regex creation failed).");
|
|
goto out;
|
|
}
|
|
|
|
g_regex_match (r, reply, 0, &match_info);
|
|
if (g_match_info_get_match_count (match_info) >= 3) {
|
|
char *str;
|
|
|
|
/* band class */
|
|
str = g_match_info_fetch (match_info, 1);
|
|
class = normalize_class (str);
|
|
g_free (str);
|
|
|
|
/* band */
|
|
str = g_match_info_fetch (match_info, 2);
|
|
band = normalize_band (str, &override_class);
|
|
if (override_class)
|
|
class = override_class;
|
|
g_free (str);
|
|
|
|
/* sid */
|
|
str = g_match_info_fetch (match_info, 3);
|
|
sid = convert_sid (str);
|
|
g_free (str);
|
|
|
|
success = TRUE;
|
|
}
|
|
|
|
g_match_info_free (match_info);
|
|
g_regex_unref (r);
|
|
}
|
|
|
|
if (success) {
|
|
gboolean class_ok = FALSE, band_ok = FALSE;
|
|
|
|
/* Normalize the SID */
|
|
if (sid < 0 || sid > 32767)
|
|
sid = 99999;
|
|
|
|
if (class == 1 || class == 2)
|
|
class_ok = TRUE;
|
|
if (band != 'Z')
|
|
band_ok = TRUE;
|
|
|
|
/* Return 'no service' if none of the elements of the +CSS response
|
|
* indicate that the modem has service. Note that this allows SID 0
|
|
* when at least one of the other elements indicates service.
|
|
* Normally we'd treat SID 0 as 'no service' but some modems
|
|
* (Sierra 5725) sometimes return SID 0 even when registered.
|
|
*/
|
|
if (sid == 0 && !class_ok && !band_ok)
|
|
sid = 99999;
|
|
|
|
/* 99999 means unknown/no service */
|
|
if (sid == 99999)
|
|
info->error = new_css_no_service_error ();
|
|
else {
|
|
mm_callback_info_set_data (info, "class", GUINT_TO_POINTER (class), NULL);
|
|
mm_callback_info_set_data (info, "band", GUINT_TO_POINTER ((guint32) band), NULL);
|
|
mm_callback_info_set_data (info, "sid", GUINT_TO_POINTER (sid), NULL);
|
|
}
|
|
} else {
|
|
info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
|
|
"Could not parse Serving System results.");
|
|
}
|
|
|
|
out:
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
legacy_get_serving_system (MMGenericCdma *self, MMCallbackInfo *info)
|
|
{
|
|
MMAtSerialPort *port;
|
|
|
|
port = mm_generic_cdma_get_best_at_port (self, &info->error);
|
|
if (port)
|
|
mm_at_serial_port_queue_command (port, "+CSS?", 3, serving_system_done, info);
|
|
else
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
cdma_status_cb (MMQcdmSerialPort *port,
|
|
GByteArray *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = user_data;
|
|
QCDMResult *result;
|
|
guint32 sid, rxstate;
|
|
|
|
if (error)
|
|
goto error;
|
|
|
|
/* Parse the response */
|
|
result = qcdm_cmd_cdma_status_result ((const char *) response->data, response->len, &info->error);
|
|
if (!result)
|
|
goto error;
|
|
|
|
qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_RX_STATE, &rxstate);
|
|
qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_SID, &sid);
|
|
qcdm_result_unref (result);
|
|
|
|
if (rxstate == QCDM_CMD_CDMA_STATUS_RX_STATE_ENTERING_CDMA)
|
|
info->error = new_css_no_service_error ();
|
|
else {
|
|
mm_callback_info_set_data (info, "class", GUINT_TO_POINTER (0), NULL);
|
|
mm_callback_info_set_data (info, "band", GUINT_TO_POINTER ((guint32) 'Z'), NULL);
|
|
mm_callback_info_set_data (info, "sid", GUINT_TO_POINTER (sid), NULL);
|
|
}
|
|
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
|
|
error:
|
|
/* If there was some error, fall back to use +CSS like we did before QCDM */
|
|
legacy_get_serving_system (MM_GENERIC_CDMA (info->modem), info);
|
|
}
|
|
|
|
static void
|
|
get_serving_system (MMModemCdma *modem,
|
|
MMModemCdmaServingSystemFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (modem);
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
MMCallbackInfo *info;
|
|
|
|
info = mm_callback_info_new_full (MM_MODEM (modem),
|
|
serving_system_invoke,
|
|
G_CALLBACK (callback),
|
|
user_data);
|
|
|
|
if (priv->qcdm) {
|
|
GByteArray *cdma_status;
|
|
|
|
cdma_status = g_byte_array_sized_new (25);
|
|
cdma_status->len = qcdm_cmd_cdma_status_new ((char *) cdma_status->data, 25, NULL);
|
|
g_assert (cdma_status->len);
|
|
mm_qcdm_serial_port_queue_command (priv->qcdm, cdma_status, 3, cdma_status_cb, info);
|
|
} else
|
|
legacy_get_serving_system (self, info);
|
|
}
|
|
|
|
#define CDMA_1X_STATE_TAG "cdma-1x-reg-state"
|
|
#define EVDO_STATE_TAG "evdo-reg-state"
|
|
|
|
void
|
|
mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info,
|
|
MMModemCdmaRegistrationState new_state)
|
|
{
|
|
g_return_if_fail (info != NULL);
|
|
g_return_if_fail (info->modem != NULL);
|
|
g_return_if_fail (MM_IS_GENERIC_CDMA (info->modem));
|
|
|
|
mm_callback_info_set_data (info, CDMA_1X_STATE_TAG, GUINT_TO_POINTER (new_state), NULL);
|
|
}
|
|
|
|
void
|
|
mm_generic_cdma_query_reg_state_set_callback_evdo_state (MMCallbackInfo *info,
|
|
MMModemCdmaRegistrationState new_state)
|
|
{
|
|
g_return_if_fail (info != NULL);
|
|
g_return_if_fail (info->modem != NULL);
|
|
g_return_if_fail (MM_IS_GENERIC_CDMA (info->modem));
|
|
|
|
mm_callback_info_set_data (info, EVDO_STATE_TAG, GUINT_TO_POINTER (new_state), NULL);
|
|
}
|
|
|
|
static void
|
|
registration_state_invoke (MMCallbackInfo *info)
|
|
{
|
|
MMModemCdmaRegistrationStateFn callback = (MMModemCdmaRegistrationStateFn) info->callback;
|
|
|
|
/* note: This is the MMModemCdma interface callback */
|
|
callback (MM_MODEM_CDMA (info->modem),
|
|
GPOINTER_TO_UINT (mm_callback_info_get_data (info, CDMA_1X_STATE_TAG)),
|
|
GPOINTER_TO_UINT (mm_callback_info_get_data (info, EVDO_STATE_TAG)),
|
|
info->error,
|
|
info->user_data);
|
|
}
|
|
|
|
MMCallbackInfo *
|
|
mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self,
|
|
MMModemCdmaRegistrationStateFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMGenericCdmaPrivate *priv;
|
|
MMCallbackInfo *info;
|
|
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), NULL);
|
|
g_return_val_if_fail (callback != NULL, NULL);
|
|
|
|
info = mm_callback_info_new_full (MM_MODEM (self),
|
|
registration_state_invoke,
|
|
G_CALLBACK (callback),
|
|
user_data);
|
|
|
|
/* Fill with current state */
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
mm_callback_info_set_data (info,
|
|
CDMA_1X_STATE_TAG,
|
|
GUINT_TO_POINTER (priv->cdma_1x_reg_state),
|
|
NULL);
|
|
mm_callback_info_set_data (info,
|
|
EVDO_STATE_TAG,
|
|
GUINT_TO_POINTER (priv->evdo_reg_state),
|
|
NULL);
|
|
return info;
|
|
}
|
|
|
|
static void
|
|
set_callback_1x_state_helper (MMCallbackInfo *info,
|
|
MMModemCdmaRegistrationState new_state)
|
|
{
|
|
if (info->modem) {
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
|
|
|
|
mm_generic_cdma_set_1x_registration_state (self, new_state);
|
|
mm_generic_cdma_query_reg_state_set_callback_1x_state (info, priv->cdma_1x_reg_state);
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_callback_evdo_state_helper (MMCallbackInfo *info,
|
|
MMModemCdmaRegistrationState new_state)
|
|
{
|
|
if (info->modem) {
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
|
|
|
|
mm_generic_cdma_set_evdo_registration_state (self, new_state);
|
|
mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, priv->evdo_reg_state);
|
|
}
|
|
}
|
|
|
|
static void
|
|
reg_state_query_done (MMModemCdma *cdma,
|
|
MMModemCdmaRegistrationState cdma_1x_reg_state,
|
|
MMModemCdmaRegistrationState evdo_reg_state,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
if (error)
|
|
info->error = g_error_copy (error);
|
|
else {
|
|
set_callback_1x_state_helper (info, cdma_1x_reg_state);
|
|
set_callback_evdo_state_helper (info, evdo_reg_state);
|
|
}
|
|
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
reg_query_speri_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = user_data;
|
|
MMModemCdmaRegistrationState cdma_state;
|
|
MMModemCdmaRegistrationState evdo_state;
|
|
|
|
cdma_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "tmp-cdma-state"));
|
|
evdo_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "tmp-evdo-state"));
|
|
|
|
if (!error) {
|
|
gboolean roam = FALSE;
|
|
|
|
if (mm_cdma_parse_speri_response (response->str, &roam, NULL)) {
|
|
cdma_state = roam ? MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING
|
|
: MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
|
|
evdo_state = roam ? MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING
|
|
: MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
|
|
}
|
|
}
|
|
|
|
set_callback_1x_state_helper (info, cdma_state);
|
|
set_callback_evdo_state_helper (info, evdo_state);
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
reg_query_spservice_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = user_data;
|
|
MMModemCdmaRegistrationState cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
|
|
MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
|
|
|
|
if (error)
|
|
info->error = g_error_copy (error);
|
|
else if (mm_cdma_parse_spservice_response (response->str,
|
|
&cdma_state,
|
|
&evdo_state)) {
|
|
if (MM_GENERIC_CDMA_GET_PRIVATE (info->modem)->has_speri) {
|
|
/* Get roaming status to override generic registration state */
|
|
mm_callback_info_set_data (info, "tmp-cdma-state", GUINT_TO_POINTER (cdma_state), NULL);
|
|
mm_callback_info_set_data (info, "tmp-evdo-state", GUINT_TO_POINTER (evdo_state), NULL);
|
|
mm_at_serial_port_queue_command (port, "$SPERI?", 3, reg_query_speri_done, info);
|
|
return;
|
|
}
|
|
}
|
|
|
|
set_callback_1x_state_helper (info, cdma_state);
|
|
set_callback_evdo_state_helper (info, evdo_state);
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
real_query_registration_state (MMGenericCdma *self,
|
|
MMModemCdmaRegistrationStateFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info;
|
|
MMAtSerialPort *port;
|
|
|
|
info = mm_generic_cdma_query_reg_state_callback_info_new (self, callback, user_data);
|
|
|
|
port = mm_generic_cdma_get_best_at_port (self, &info->error);
|
|
if (!port) {
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
|
|
if (MM_GENERIC_CDMA_GET_PRIVATE (self)->has_spservice) {
|
|
/* Try Sprint-specific commands */
|
|
mm_at_serial_port_queue_command (port, "+SPSERVICE?", 3, reg_query_spservice_done, info);
|
|
} else {
|
|
/* Assume we're registered if we passed CAD and CSS checking */
|
|
set_callback_1x_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED);
|
|
set_callback_evdo_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED);
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
}
|
|
|
|
static void
|
|
reg_state_css_response (MMModemCdma *cdma,
|
|
guint32 class,
|
|
unsigned char band,
|
|
guint32 sid,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
/* We'll get an error if the SID isn't valid, so detect that and
|
|
* report unknown registration state.
|
|
*/
|
|
if (error) {
|
|
if ( (error->domain == MM_MOBILE_ERROR)
|
|
&& (error->code == MM_MOBILE_ERROR_NO_NETWORK)) {
|
|
set_callback_1x_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
set_callback_evdo_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
} else {
|
|
/* Some other error parsing CSS results */
|
|
info->error = g_error_copy (error);
|
|
}
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
|
|
MM_GENERIC_CDMA_GET_CLASS (cdma)->query_registration_state (MM_GENERIC_CDMA (info->modem),
|
|
reg_state_query_done,
|
|
info);
|
|
}
|
|
|
|
static void
|
|
get_analog_digital_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
const char *reply;
|
|
long int int_cad;
|
|
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
goto error;
|
|
}
|
|
|
|
/* Strip any leading command tag and spaces */
|
|
reply = mm_strip_tag (response->str, "+CAD:");
|
|
|
|
errno = 0;
|
|
int_cad = strtol (reply, NULL, 10);
|
|
if ((errno == EINVAL) || (errno == ERANGE)) {
|
|
info->error = g_error_new_literal (MM_MODEM_ERROR,
|
|
MM_MODEM_ERROR_GENERAL,
|
|
"Failed to parse +CAD response");
|
|
goto error;
|
|
}
|
|
|
|
if (int_cad == 1) { /* 1 == CDMA service */
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
|
|
|
|
/* Now that we have some sort of service, check if the the device is
|
|
* registered on the network.
|
|
*/
|
|
|
|
/* Some devices key the AT+CSS? response off the 1X state, but if the
|
|
* device has EVDO service but no 1X service, then reading AT+CSS? will
|
|
* error out too early. Let subclasses that know that their AT+CSS?
|
|
* response is wrong in this case handle more specific registration
|
|
* themselves; if they do, they'll set priv->reg_try_css to FALSE.
|
|
*/
|
|
if (priv->reg_try_css) {
|
|
get_serving_system (MM_MODEM_CDMA (info->modem),
|
|
reg_state_css_response,
|
|
info);
|
|
} else {
|
|
/* Subclass knows that AT+CSS? will respond incorrectly to EVDO
|
|
* state, so skip AT+CSS? query.
|
|
*/
|
|
MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state (MM_GENERIC_CDMA (info->modem),
|
|
reg_state_query_done,
|
|
info);
|
|
}
|
|
return;
|
|
} else {
|
|
/* No service */
|
|
set_callback_1x_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
set_callback_evdo_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
}
|
|
|
|
error:
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
reg_cmstate_cb (MMQcdmSerialPort *port,
|
|
GByteArray *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = user_data;
|
|
MMAtSerialPort *at_port;
|
|
QCDMResult *result;
|
|
guint32 opmode = 0, sysmode = 0;
|
|
MMModemCdmaRegistrationState cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
|
|
MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
|
|
|
|
if (error)
|
|
goto error;
|
|
|
|
/* Parse the response */
|
|
result = qcdm_cmd_cm_subsys_state_info_result ((const char *) response->data, response->len, &info->error);
|
|
if (!result)
|
|
goto error;
|
|
|
|
qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &opmode);
|
|
qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, &sysmode);
|
|
qcdm_result_unref (result);
|
|
|
|
if (opmode == QCDM_CMD_CM_SUBSYS_STATE_INFO_OPERATING_MODE_ONLINE) {
|
|
switch (sysmode) {
|
|
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_CDMA:
|
|
cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
|
|
break;
|
|
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_HDR:
|
|
evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
|
|
break;
|
|
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_AMPS:
|
|
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_NO_SERVICE:
|
|
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_WCDMA:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (cdma_state || evdo_state) {
|
|
/* Device is registered to something; see if the subclass has a
|
|
* better idea of whether we're roaming or not and what the
|
|
* access technology is.
|
|
*/
|
|
if (MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state)
|
|
MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state (MM_GENERIC_CDMA (info->modem),
|
|
reg_state_query_done,
|
|
info);
|
|
return;
|
|
}
|
|
}
|
|
|
|
set_callback_1x_state_helper (info, cdma_state);
|
|
set_callback_evdo_state_helper (info, evdo_state);
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
|
|
error:
|
|
/* If there was some error, fall back to use +CAD like we did before QCDM */
|
|
at_port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (info->modem), &info->error);
|
|
if (at_port)
|
|
mm_at_serial_port_queue_command (at_port, "+CAD?", 3, get_analog_digital_done, info);
|
|
else
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
get_registration_state (MMModemCdma *modem,
|
|
MMModemCdmaRegistrationStateFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
|
|
MMCallbackInfo *info;
|
|
MMAtSerialPort *port;
|
|
|
|
info = mm_generic_cdma_query_reg_state_callback_info_new (MM_GENERIC_CDMA (modem), callback, user_data);
|
|
|
|
port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error);
|
|
if (!port && !priv->qcdm) {
|
|
g_message ("Returning saved registration states: 1x: %d EVDO: %d",
|
|
priv->cdma_1x_reg_state, priv->evdo_reg_state);
|
|
mm_callback_info_set_data (info,
|
|
CDMA_1X_STATE_TAG,
|
|
GUINT_TO_POINTER (priv->cdma_1x_reg_state),
|
|
NULL);
|
|
mm_callback_info_set_data (info,
|
|
EVDO_STATE_TAG,
|
|
GUINT_TO_POINTER (priv->evdo_reg_state),
|
|
NULL);
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
|
|
/* Use QCDM for Call Manager state or HDR state before trying CAD, since
|
|
* CAD doesn't always reflect the state of the HDR radio's registration
|
|
* status.
|
|
*/
|
|
if (priv->qcdm) {
|
|
GByteArray *cmstate;
|
|
|
|
cmstate = g_byte_array_sized_new (25);
|
|
cmstate->len = qcdm_cmd_cm_subsys_state_info_new ((char *) cmstate->data, 25, NULL);
|
|
g_assert (cmstate->len);
|
|
mm_qcdm_serial_port_queue_command (priv->qcdm, cmstate, 3, reg_cmstate_cb, info);
|
|
} else
|
|
mm_at_serial_port_queue_command (port, "+CAD?", 3, get_analog_digital_done, info);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* MMModemSimple interface */
|
|
|
|
typedef enum {
|
|
SIMPLE_STATE_BEGIN = 0,
|
|
SIMPLE_STATE_ENABLE,
|
|
SIMPLE_STATE_REGISTER,
|
|
SIMPLE_STATE_CONNECT,
|
|
SIMPLE_STATE_DONE
|
|
} SimpleState;
|
|
|
|
static const char *
|
|
simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error)
|
|
{
|
|
GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties");
|
|
GValue *value;
|
|
|
|
value = (GValue *) g_hash_table_lookup (properties, name);
|
|
if (!value)
|
|
return NULL;
|
|
|
|
if (G_VALUE_HOLDS_STRING (value))
|
|
return g_value_get_string (value);
|
|
|
|
g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
|
|
"Invalid property type for '%s': %s (string expected)",
|
|
name, G_VALUE_TYPE_NAME (value));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
simple_reg_retry (gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (info->modem),
|
|
simple_reg_callback,
|
|
info);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
simple_reg_callback (MMModemCdma *modem,
|
|
MMModemCdmaRegistrationState cdma_1x_reg_state,
|
|
MMModemCdmaRegistrationState evdo_reg_state,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
|
|
gboolean no_service_error = FALSE;
|
|
|
|
if ( error
|
|
&& (error->domain == MM_MOBILE_ERROR)
|
|
&& (error->code == MM_MOBILE_ERROR_NO_NETWORK))
|
|
no_service_error = TRUE;
|
|
|
|
/* Fail immediately on anything but "no service" */
|
|
if (error && !no_service_error) {
|
|
simple_state_machine (MM_MODEM (modem), error, info);
|
|
return;
|
|
}
|
|
|
|
if ( no_service_error
|
|
|| ( (cdma_1x_reg_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
|
|
&& (evdo_reg_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN))) {
|
|
/* Not registered yet, queue up a retry */
|
|
priv->reg_tries++;
|
|
if (priv->reg_tries > 15) {
|
|
error = g_error_new_literal (MM_MOBILE_ERROR,
|
|
MM_MOBILE_ERROR_NO_NETWORK,
|
|
"No service");
|
|
simple_state_machine (MM_MODEM (modem), error, info);
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
/* otherwise, just try again in a bit */
|
|
if (!priv->reg_retry_id)
|
|
priv->reg_retry_id = g_timeout_add_seconds (4, simple_reg_retry, info);
|
|
} else {
|
|
/* Yay, at least one of 1x or EVDO is registered, we can proceed to dial */
|
|
simple_state_machine (MM_MODEM (modem), NULL, info);
|
|
}
|
|
}
|
|
|
|
static void
|
|
reg_state_changed (MMModemCdma *self,
|
|
MMModemCdmaRegistrationState cdma_1x_new_state,
|
|
MMModemCdmaRegistrationState evdo_new_state,
|
|
gpointer user_data)
|
|
{
|
|
/* Disabled for now... changing the registration state from the
|
|
* subclass' query_registration_state handler also emits the registration
|
|
* state changed signal, which will call this function, and execute
|
|
* simple_state_machine() to advance to the next state. Then however
|
|
* query_registration_state will call its callback, which ends up in
|
|
* simple_reg_callback(), which calls simple_state_machine() too in
|
|
* the same mainloop iteration. Not good. So until that's sorted out
|
|
* we'll just have to poll registration state (every 4 seconds so its
|
|
* not that bad.
|
|
*/
|
|
#if 0
|
|
MMCallbackInfo *info = user_data;
|
|
|
|
/* If we're registered, we can proceed */
|
|
if ( (cdma_1x_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
|
|
|| (evdo_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN))
|
|
simple_state_machine (MM_MODEM (modem), NULL, info);
|
|
#endif
|
|
}
|
|
|
|
static SimpleState
|
|
set_simple_state (MMCallbackInfo *info, SimpleState state)
|
|
{
|
|
mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (state), NULL);
|
|
return state;
|
|
}
|
|
|
|
static void
|
|
simple_state_machine (MMModem *modem, GError *error, gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
|
|
SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state"));
|
|
const char *str;
|
|
guint id;
|
|
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
goto out;
|
|
}
|
|
|
|
switch (state) {
|
|
case SIMPLE_STATE_BEGIN:
|
|
state = set_simple_state (info, SIMPLE_STATE_ENABLE);
|
|
mm_modem_enable (modem, simple_state_machine, info);
|
|
break;
|
|
case SIMPLE_STATE_ENABLE:
|
|
state = set_simple_state (info, SIMPLE_STATE_REGISTER);
|
|
mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (modem),
|
|
simple_reg_callback,
|
|
info);
|
|
id = g_signal_connect (modem,
|
|
MM_MODEM_CDMA_REGISTRATION_STATE_CHANGED,
|
|
G_CALLBACK (reg_state_changed),
|
|
info);
|
|
priv->reg_state_changed_id = id;
|
|
break;
|
|
case SIMPLE_STATE_REGISTER:
|
|
registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0);
|
|
state = set_simple_state (info, SIMPLE_STATE_CONNECT);
|
|
mm_modem_set_state (modem, MM_MODEM_STATE_REGISTERED, MM_MODEM_STATE_REASON_NONE);
|
|
|
|
str = simple_get_string_property (info, "number", &info->error);
|
|
mm_modem_connect (modem, str, simple_state_machine, info);
|
|
break;
|
|
case SIMPLE_STATE_CONNECT:
|
|
state = set_simple_state (info, SIMPLE_STATE_DONE);
|
|
break;
|
|
case SIMPLE_STATE_DONE:
|
|
break;
|
|
}
|
|
|
|
out:
|
|
if (info->error || state == SIMPLE_STATE_DONE) {
|
|
registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0);
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
}
|
|
|
|
static void
|
|
simple_connect (MMModemSimple *simple,
|
|
GHashTable *properties,
|
|
MMModemFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (simple);
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
MMCallbackInfo *info;
|
|
GError *error = NULL;
|
|
|
|
if (priv->simple_connect_info) {
|
|
error = g_error_new_literal (MM_MODEM_ERROR,
|
|
MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
|
|
"Connection is already in progress");
|
|
callback (MM_MODEM (simple), error, user_data);
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
info = mm_callback_info_new (MM_MODEM (simple), callback, user_data);
|
|
priv->simple_connect_info = info;
|
|
mm_callback_info_set_data (info, "simple-connect-properties",
|
|
g_hash_table_ref (properties),
|
|
(GDestroyNotify) g_hash_table_unref);
|
|
|
|
/* At least number must be present */
|
|
if (!simple_get_string_property (info, "number", &error)) {
|
|
if (!error)
|
|
error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Missing number property");
|
|
}
|
|
|
|
simple_state_machine (MM_MODEM (simple), error, info);
|
|
}
|
|
|
|
static void
|
|
simple_free_gvalue (gpointer data)
|
|
{
|
|
g_value_unset ((GValue *) data);
|
|
g_slice_free (GValue, data);
|
|
}
|
|
|
|
static GValue *
|
|
simple_uint_value (guint32 i)
|
|
{
|
|
GValue *val;
|
|
|
|
val = g_slice_new0 (GValue);
|
|
g_value_init (val, G_TYPE_UINT);
|
|
g_value_set_uint (val, i);
|
|
|
|
return val;
|
|
}
|
|
|
|
#define SS_HASH_TAG "simple-get-status"
|
|
|
|
static void
|
|
simple_status_got_signal_quality (MMModem *modem,
|
|
guint32 result,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
GHashTable *properties;
|
|
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
g_warning ("Error getting signal quality: %s", error->message);
|
|
} else {
|
|
properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG);
|
|
g_hash_table_insert (properties, "signal_quality", simple_uint_value (result));
|
|
}
|
|
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
simple_get_status_invoke (MMCallbackInfo *info)
|
|
{
|
|
MMModemSimpleGetStatusFn callback = (MMModemSimpleGetStatusFn) info->callback;
|
|
|
|
callback (MM_MODEM_SIMPLE (info->modem),
|
|
(GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG),
|
|
info->error, info->user_data);
|
|
}
|
|
|
|
static void
|
|
simple_get_status (MMModemSimple *simple,
|
|
MMModemSimpleGetStatusFn callback,
|
|
gpointer user_data)
|
|
{
|
|
GHashTable *properties;
|
|
MMCallbackInfo *info;
|
|
|
|
info = mm_callback_info_new_full (MM_MODEM (simple),
|
|
simple_get_status_invoke,
|
|
G_CALLBACK (callback),
|
|
user_data);
|
|
|
|
properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, simple_free_gvalue);
|
|
mm_callback_info_set_data (info, SS_HASH_TAG, properties, (GDestroyNotify) g_hash_table_unref);
|
|
mm_modem_cdma_get_signal_quality (MM_MODEM_CDMA (simple), simple_status_got_signal_quality, info);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
modem_valid_changed (MMGenericCdma *self, GParamSpec *pspec, gpointer user_data)
|
|
{
|
|
/* Be paranoid about tearing down any pending registration */
|
|
if (!mm_modem_get_valid (MM_MODEM (self)))
|
|
registration_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL);
|
|
}
|
|
|
|
static void
|
|
modem_state_changed (MMGenericCdma *self, GParamSpec *pspec, gpointer user_data)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
MMModemState state;
|
|
|
|
/* Start polling registration status and signal quality when enabled */
|
|
|
|
state = mm_modem_get_state (MM_MODEM (self));
|
|
if (state >= MM_MODEM_STATE_ENABLED) {
|
|
if (!priv->poll_id)
|
|
priv->poll_id = g_timeout_add_seconds (30, periodic_poll_cb, self);
|
|
} else {
|
|
if (priv->poll_id)
|
|
g_source_remove (priv->poll_id);
|
|
priv->poll_id = 0;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
modem_init (MMModem *modem_class)
|
|
{
|
|
modem_class->owns_port = owns_port;
|
|
modem_class->grab_port = grab_port;
|
|
modem_class->release_port = release_port;
|
|
modem_class->enable = enable;
|
|
modem_class->disable = disable;
|
|
modem_class->connect = connect;
|
|
modem_class->disconnect = disconnect;
|
|
modem_class->get_info = get_card_info;
|
|
}
|
|
|
|
static void
|
|
modem_cdma_init (MMModemCdma *cdma_class)
|
|
{
|
|
cdma_class->get_signal_quality = get_signal_quality;
|
|
cdma_class->get_esn = get_esn;
|
|
cdma_class->get_serving_system = get_serving_system;
|
|
cdma_class->get_registration_state = get_registration_state;
|
|
}
|
|
|
|
static void
|
|
modem_simple_init (MMModemSimple *class)
|
|
{
|
|
class->connect = simple_connect;
|
|
class->get_status = simple_get_status;
|
|
}
|
|
|
|
static void
|
|
mm_generic_cdma_init (MMGenericCdma *self)
|
|
{
|
|
g_signal_connect (self, "notify::" MM_MODEM_VALID,
|
|
G_CALLBACK (modem_valid_changed), NULL);
|
|
g_signal_connect (self, "notify::" MM_MODEM_STATE,
|
|
G_CALLBACK (modem_state_changed), NULL);
|
|
}
|
|
|
|
static void
|
|
set_property (GObject *object, guint prop_id,
|
|
const GValue *value, GParamSpec *pspec)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (object);
|
|
|
|
switch (prop_id) {
|
|
case MM_MODEM_PROP_TYPE:
|
|
break;
|
|
case PROP_EVDO_REV0:
|
|
priv->evdo_rev0 = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_EVDO_REVA:
|
|
priv->evdo_revA = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_REG_TRY_CSS:
|
|
priv->reg_try_css = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_property (GObject *object, guint prop_id,
|
|
GValue *value, GParamSpec *pspec)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (object);
|
|
|
|
switch (prop_id) {
|
|
case MM_MODEM_PROP_DATA_DEVICE:
|
|
if (priv->data)
|
|
g_value_set_string (value, mm_port_get_device (priv->data));
|
|
else
|
|
g_value_set_string (value, NULL);
|
|
break;
|
|
case MM_MODEM_PROP_TYPE:
|
|
g_value_set_uint (value, MM_MODEM_TYPE_CDMA);
|
|
break;
|
|
case PROP_EVDO_REV0:
|
|
g_value_set_boolean (value, priv->evdo_rev0);
|
|
break;
|
|
case PROP_EVDO_REVA:
|
|
g_value_set_boolean (value, priv->evdo_revA);
|
|
break;
|
|
case PROP_REG_TRY_CSS:
|
|
g_value_set_boolean (value, priv->reg_try_css);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dispose (GObject *object)
|
|
{
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (object);
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
registration_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL);
|
|
|
|
if (priv->poll_id)
|
|
g_source_remove (priv->poll_id);
|
|
|
|
G_OBJECT_CLASS (mm_generic_cdma_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
mm_generic_cdma_class_init (MMGenericCdmaClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
mm_generic_cdma_parent_class = g_type_class_peek_parent (klass);
|
|
g_type_class_add_private (object_class, sizeof (MMGenericCdmaPrivate));
|
|
|
|
/* Virtual methods */
|
|
object_class->set_property = set_property;
|
|
object_class->get_property = get_property;
|
|
object_class->dispose = dispose;
|
|
klass->query_registration_state = real_query_registration_state;
|
|
|
|
/* Properties */
|
|
g_object_class_override_property (object_class,
|
|
MM_MODEM_PROP_DATA_DEVICE,
|
|
MM_MODEM_DATA_DEVICE);
|
|
|
|
g_object_class_override_property (object_class,
|
|
MM_MODEM_PROP_TYPE,
|
|
MM_MODEM_TYPE);
|
|
|
|
g_object_class_install_property (object_class, PROP_EVDO_REV0,
|
|
g_param_spec_boolean (MM_GENERIC_CDMA_EVDO_REV0,
|
|
"EVDO rev0",
|
|
"Supports EVDO rev0",
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property (object_class, PROP_EVDO_REVA,
|
|
g_param_spec_boolean (MM_GENERIC_CDMA_EVDO_REVA,
|
|
"EVDO revA",
|
|
"Supports EVDO revA",
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property (object_class, PROP_REG_TRY_CSS,
|
|
g_param_spec_boolean (MM_GENERIC_CDMA_REGISTRATION_TRY_CSS,
|
|
"RegistrationTryCss",
|
|
"Use Serving System response when checking modem"
|
|
" registration state.",
|
|
TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
}
|
|
|