Make port roles more flexible. We have modems that do PPP on interfaces other than the primary interface, and that wasn't possible with the old code. So clean up all that logic and move the port organization code into the core so we can reduce code in the plugins. In the new world order, the plugins say whether the port is a QCDM port, an AT port, or ignored. If it's an AT port the plugins get to tag it as primary, secondary, or PPP, or any combination of the 3. This allows for modems where PPP should really be done on the secondary port (Huawei E220, Sierra devices) so that the primary port stays open for command and status. Modem subclasses no longer get asked to handle port grabbing themselves. Instead, that's now done by the generic classes (MMGenericCdma and MMGenericGsm) and the plugins are notified when a port is grabbed so they can add unsolicited response handlers for it. After all ports are grabbed by the generic classes, they get "organized", which assigns various ports to the roles of PRIMARY, SECONDARY, DATA, and QCDM based on specific rules and hints that the plugin provided (which are expressed as MMAtPortFlags). The plugins then have a chance to perform fixups on the primary port if they choose. The plugin code is responsible for determining the port hints (ie MMAtPortFlags) at probe time, instead of having a combination of the plugin and the modem class do the job. This simplifies things greatly for the plugins at the expense of more complicated logic in the core.
2627 lines
88 KiB
C
2627 lines
88 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"
|
|
#include "libqcdm/src/errors.h"
|
|
#include "mm-log.h"
|
|
|
|
#define MM_GENERIC_CDMA_PREV_STATE_TAG "prev-state"
|
|
|
|
typedef enum {
|
|
RM_PROTO_ASYNC = 0,
|
|
RM_PROTO_RELAY = 1,
|
|
RM_PROTO_NETWORK_PPP = 2,
|
|
RM_PROTO_NETWORK_SLIP = 3,
|
|
RM_PROTO_STU_III = 4
|
|
} RmProtocol;
|
|
|
|
|
|
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;
|
|
|
|
/* Original and current Rm interface protocol */
|
|
RmProtocol orig_crm;
|
|
RmProtocol cur_crm;
|
|
|
|
guint poll_id;
|
|
|
|
char *meid;
|
|
|
|
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;
|
|
gboolean data_opened_at_connect;
|
|
} 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,
|
|
guint vendor,
|
|
guint product)
|
|
{
|
|
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,
|
|
MM_MODEM_HW_VID, vendor,
|
|
MM_MODEM_HW_PID, product,
|
|
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 void
|
|
get_esn_cb (MMModem *modem,
|
|
const char *result,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
if (modem) {
|
|
mm_modem_base_set_equipment_identifier (MM_MODEM_BASE (modem), error ? "" : result);
|
|
mm_serial_port_close (MM_SERIAL_PORT (MM_GENERIC_CDMA_GET_PRIVATE (modem)->primary));
|
|
check_valid (MM_GENERIC_CDMA (modem));
|
|
}
|
|
}
|
|
|
|
static void
|
|
initial_esn_check (MMGenericCdma *self)
|
|
{
|
|
GError *error = NULL;
|
|
MMGenericCdmaPrivate *priv;
|
|
|
|
g_return_if_fail (MM_IS_GENERIC_CDMA (self));
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
g_return_if_fail (priv->primary != NULL);
|
|
|
|
if (mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) {
|
|
/* Make sure echoing is off */
|
|
mm_at_serial_port_queue_command (priv->primary, "E0", 3, NULL, NULL);
|
|
mm_modem_cdma_get_esn (MM_MODEM_CDMA (self), get_esn_cb, NULL);
|
|
} else {
|
|
g_warning ("%s: failed to open serial port: (%d) %s",
|
|
__func__,
|
|
error ? error->code : -1,
|
|
error && error->message ? error->message : "(unknown)");
|
|
g_clear_error (&error);
|
|
check_valid (self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_info_cb (MMModem *modem,
|
|
const char *manufacturer,
|
|
const char *model,
|
|
const char *version,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
/* Base class handles saving the info for us */
|
|
if (modem)
|
|
mm_serial_port_close (MM_SERIAL_PORT (MM_GENERIC_CDMA_GET_PRIVATE (modem)->primary));
|
|
}
|
|
|
|
static void
|
|
initial_info_check (MMGenericCdma *self)
|
|
{
|
|
GError *error = NULL;
|
|
MMGenericCdmaPrivate *priv;
|
|
|
|
g_return_if_fail (MM_IS_GENERIC_CDMA (self));
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
g_return_if_fail (priv->primary != NULL);
|
|
|
|
if (mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) {
|
|
/* Make sure echoing is off */
|
|
mm_at_serial_port_queue_command (priv->primary, "E0", 3, NULL, NULL);
|
|
mm_modem_base_get_card_info (MM_MODEM_BASE (self),
|
|
priv->primary,
|
|
NULL,
|
|
get_info_cb,
|
|
NULL);
|
|
} else {
|
|
g_warning ("%s: failed to open serial port: (%d) %s",
|
|
__func__,
|
|
error ? error->code : -1,
|
|
error && error->message ? error->message : "(unknown)");
|
|
g_clear_error (&error);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
owns_port (MMModem *modem, const char *subsys, const char *name)
|
|
{
|
|
return !!mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name);
|
|
}
|
|
|
|
static void
|
|
port_grabbed (MMModemBase *base,
|
|
MMPort *port,
|
|
MMAtPortFlags at_pflags,
|
|
gpointer user_data)
|
|
{
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (base);
|
|
|
|
if (MM_IS_AT_SERIAL_PORT (port)) {
|
|
g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL);
|
|
mm_at_serial_port_set_flags (MM_AT_SERIAL_PORT (port), at_pflags);
|
|
|
|
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 (MM_GENERIC_CDMA_GET_CLASS (self)->port_grabbed)
|
|
MM_GENERIC_CDMA_GET_CLASS (self)->port_grabbed (self, port, at_pflags, user_data);
|
|
}
|
|
|
|
static gboolean
|
|
organize_ports (MMModem *modem, GError **error)
|
|
{
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (modem);
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
if (!mm_modem_base_organize_ports (MM_MODEM_BASE (modem),
|
|
&priv->primary,
|
|
&priv->secondary,
|
|
&priv->data,
|
|
&priv->qcdm,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* Let subclasses twiddle ports if they want */
|
|
if (MM_GENERIC_CDMA_GET_CLASS (self)->ports_organized)
|
|
MM_GENERIC_CDMA_GET_CLASS (self)->ports_organized (self, priv->primary);
|
|
|
|
g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE);
|
|
|
|
/* Get the modem's general info */
|
|
initial_info_check (self);
|
|
|
|
/* Get modem's ESN number */
|
|
initial_esn_check (self);
|
|
|
|
check_valid (self);
|
|
return TRUE;
|
|
}
|
|
|
|
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,
|
|
MMAtPortFlags flag)
|
|
{
|
|
MMGenericCdmaPrivate *priv;
|
|
|
|
g_return_val_if_fail (MM_IS_GENERIC_CDMA (modem), NULL);
|
|
|
|
/* We only search for a single value even though it's a bitfield */
|
|
g_return_val_if_fail ( flag == MM_AT_PORT_FLAG_NONE
|
|
|| flag == MM_AT_PORT_FLAG_PRIMARY
|
|
|| flag == MM_AT_PORT_FLAG_SECONDARY
|
|
|| flag == MM_AT_PORT_FLAG_PPP, NULL);
|
|
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (modem);
|
|
|
|
if (flag == MM_AT_PORT_FLAG_SECONDARY)
|
|
return priv->secondary;
|
|
else if (flag == MM_AT_PORT_FLAG_PRIMARY)
|
|
return priv->primary;
|
|
else if ((flag == MM_AT_PORT_FLAG_PPP) && MM_IS_AT_SERIAL_PORT (priv->data))
|
|
return MM_AT_SERIAL_PORT (priv->data);
|
|
|
|
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;
|
|
}
|
|
|
|
MMQcdmSerialPort *
|
|
mm_generic_cdma_get_best_qcdm_port (MMGenericCdma *self, GError **error)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), NULL);
|
|
|
|
return MM_GENERIC_CDMA_GET_PRIVATE (self)->qcdm;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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
|
|
crm_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
const char *p;
|
|
unsigned long num;
|
|
|
|
if (error)
|
|
return;
|
|
|
|
p = mm_strip_tag (response->str, "+CRM:");
|
|
if (p) {
|
|
errno = 0;
|
|
num = strtoul (p, NULL, 10);
|
|
if (num <= 4 && (errno == 0)) {
|
|
MM_GENERIC_CDMA_GET_PRIVATE (user_data)->orig_crm = (guint32) num;
|
|
MM_GENERIC_CDMA_GET_PRIVATE (user_data)->cur_crm = (guint32) num;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
/* Grab default CRM */
|
|
mm_at_serial_port_queue_command (priv->primary, "+CRM?", 3, crm_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
|
|
init_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
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 {
|
|
MMGenericCdma *self = MM_GENERIC_CDMA (info->modem);
|
|
|
|
/* Try enabling better error reporting on CDMA devices, but few
|
|
* actually support +CMEE as it's more of a GSM command.
|
|
*/
|
|
mm_at_serial_port_queue_command (port, "+CMEE=1", 3, NULL, NULL);
|
|
|
|
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
|
|
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, FALSE, 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;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (!modem || mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
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_force (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;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
|
|
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_force (MM_SERIAL_PORT (priv->secondary));
|
|
if (priv->qcdm)
|
|
mm_serial_port_close_force (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, TRUE, 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;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
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;
|
|
MMAtSerialPort *dial_port;
|
|
|
|
info = mm_callback_info_new (modem, callback, user_data);
|
|
|
|
/* Dial port might not be the primary port*/
|
|
priv->data_opened_at_connect = FALSE;
|
|
dial_port = priv->primary;
|
|
if (MM_IS_AT_SERIAL_PORT (priv->data)) {
|
|
dial_port = MM_AT_SERIAL_PORT (priv->data);
|
|
|
|
if (!mm_serial_port_open (MM_SERIAL_PORT (dial_port), &info->error)) {
|
|
g_warning ("%s: failed to open dial port: (%d) %s",
|
|
__func__,
|
|
info->error ? info->error->code : -1,
|
|
info->error && info->error->message ? info->error->message : "(unknown)");
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
priv->data_opened_at_connect = TRUE;
|
|
}
|
|
|
|
mm_modem_set_state (modem, MM_MODEM_STATE_CONNECTING, MM_MODEM_STATE_REASON_NONE);
|
|
|
|
command = g_strconcat ("DT", number, NULL);
|
|
mm_at_serial_port_queue_command (dial_port, 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;
|
|
MMGenericCdma *self;
|
|
MMGenericCdmaPrivate *priv;
|
|
MMModemState prev_state;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
self = MM_GENERIC_CDMA (info->modem);
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
|
|
/* 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 (priv->data, FALSE);
|
|
update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE);
|
|
}
|
|
|
|
/* Balance any open from connect(); subclasses may not use the generic
|
|
* class' connect function and so the dial port may not have been
|
|
* opened at all.
|
|
*/
|
|
if (priv->data_opened_at_connect) {
|
|
if (MM_IS_AT_SERIAL_PORT (port))
|
|
mm_serial_port_close (port);
|
|
priv->data_opened_at_connect = FALSE;
|
|
}
|
|
|
|
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;
|
|
MMAtSerialPort *dial_port;
|
|
|
|
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);
|
|
|
|
dial_port = priv->primary;
|
|
if (MM_IS_AT_SERIAL_PORT (priv->data))
|
|
dial_port = MM_AT_SERIAL_PORT (priv->data);
|
|
|
|
mm_serial_port_flash (MM_SERIAL_PORT (dial_port), 1000, TRUE, 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;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
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 {
|
|
const char *reply = response->str;
|
|
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;
|
|
int err = QCDM_SUCCESS;
|
|
|
|
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, &err);
|
|
if (!result) {
|
|
g_set_error (&info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
|
|
"Failed to parse pilot sets command result: %d", err);
|
|
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) {
|
|
mm_dbg ("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);
|
|
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 the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
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;
|
|
int class = 0, sid = 99999, num;
|
|
unsigned char band = 'Z';
|
|
gboolean success = FALSE;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
goto out;
|
|
}
|
|
|
|
reply = response->str;
|
|
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;
|
|
int err = QCDM_SUCCESS;
|
|
|
|
if (error)
|
|
goto error;
|
|
|
|
/* Parse the response */
|
|
result = qcdm_cmd_cdma_status_result ((const char *) response->data, response->len, &err);
|
|
if (!result) {
|
|
g_set_error (&info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
|
|
"Failed to parse cdma status command result: %d", err);
|
|
goto error;
|
|
}
|
|
|
|
qcdm_result_get_u32 (result, QCDM_CMD_CDMA_STATUS_ITEM_RX_STATE, &rxstate);
|
|
qcdm_result_get_u32 (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);
|
|
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);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/* Registration state stuff */
|
|
|
|
#define CDMA_1X_STATE_TAG "cdma-1x-reg-state"
|
|
#define EVDO_STATE_TAG "evdo-reg-state"
|
|
|
|
MMModemCdmaRegistrationState
|
|
mm_generic_cdma_query_reg_state_get_callback_1x_state (MMCallbackInfo *info)
|
|
{
|
|
g_return_val_if_fail (info != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
g_return_val_if_fail (info->modem != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
g_return_val_if_fail (MM_IS_GENERIC_CDMA (info->modem), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
|
|
return GPOINTER_TO_UINT (mm_callback_info_get_data (info, CDMA_1X_STATE_TAG));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
MMModemCdmaRegistrationState
|
|
mm_generic_cdma_query_reg_state_get_callback_evdo_state (MMCallbackInfo *info)
|
|
{
|
|
g_return_val_if_fail (info != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
g_return_val_if_fail (info->modem != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
g_return_val_if_fail (MM_IS_GENERIC_CDMA (info->modem), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
|
|
|
|
return GPOINTER_TO_UINT (mm_callback_info_get_data (info, EVDO_STATE_TAG));
|
|
}
|
|
|
|
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),
|
|
mm_generic_cdma_query_reg_state_get_callback_1x_state (info),
|
|
mm_generic_cdma_query_reg_state_get_callback_evdo_state (info),
|
|
info->error,
|
|
info->user_data);
|
|
}
|
|
|
|
MMCallbackInfo *
|
|
mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self,
|
|
MMModemCdmaRegistrationState cur_cdma_state,
|
|
MMModemCdmaRegistrationState cur_evdo_state,
|
|
MMModemCdmaRegistrationStateFn callback,
|
|
gpointer user_data)
|
|
{
|
|
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 */
|
|
mm_generic_cdma_query_reg_state_set_callback_1x_state (info, cur_cdma_state);
|
|
mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, cur_evdo_state);
|
|
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
|
|
subclass_reg_query_done (MMModemCdma *cdma,
|
|
MMModemCdmaRegistrationState cdma_reg_state,
|
|
MMModemCdmaRegistrationState evdo_reg_state,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
if (error)
|
|
info->error = g_error_copy (error);
|
|
else {
|
|
/* Set final registration state */
|
|
set_callback_1x_state_helper (info, cdma_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;
|
|
gboolean roam = FALSE;
|
|
const char *p;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
if (error)
|
|
goto done;
|
|
|
|
p = mm_strip_tag (response->str, "$SPERI:");
|
|
if (!p || !mm_cdma_parse_eri (p, &roam, NULL, NULL))
|
|
goto done;
|
|
|
|
if (roam) {
|
|
/* Change the 1x and EVDO registration states to roaming if they were
|
|
* anything other than UNKNOWN.
|
|
*/
|
|
if (mm_generic_cdma_query_reg_state_get_callback_1x_state (info))
|
|
mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING);
|
|
|
|
if (mm_generic_cdma_query_reg_state_get_callback_evdo_state (info))
|
|
mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING);
|
|
} else {
|
|
/* Change 1x and/or EVDO registration state to home if home/roaming wasn't previously known */
|
|
if (mm_generic_cdma_query_reg_state_get_callback_1x_state (info) == MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED)
|
|
mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
|
|
|
|
if (mm_generic_cdma_query_reg_state_get_callback_evdo_state (info) == MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED)
|
|
mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
|
|
}
|
|
|
|
done:
|
|
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 the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
if (error)
|
|
info->error = g_error_copy (error);
|
|
else if (mm_cdma_parse_spservice_response (response->str, &cdma_state, &evdo_state)) {
|
|
mm_generic_cdma_query_reg_state_set_callback_1x_state (info, cdma_state);
|
|
mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, evdo_state);
|
|
|
|
if (MM_GENERIC_CDMA_GET_PRIVATE (info->modem)->has_speri) {
|
|
/* Get roaming status to override generic registration state */
|
|
mm_at_serial_port_queue_command (port, "$SPERI?", 3, reg_query_speri_done, info);
|
|
return;
|
|
}
|
|
}
|
|
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
real_query_registration_state (MMGenericCdma *self,
|
|
MMModemCdmaRegistrationState cur_cdma_state,
|
|
MMModemCdmaRegistrationState cur_evdo_state,
|
|
MMModemCdmaRegistrationStateFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info;
|
|
MMAtSerialPort *port;
|
|
|
|
/* Seed this CallbackInfo with any previously determined registration state */
|
|
info = mm_generic_cdma_query_reg_state_callback_info_new (self,
|
|
cur_cdma_state,
|
|
cur_evdo_state,
|
|
callback,
|
|
user_data);
|
|
|
|
port = mm_generic_cdma_get_best_at_port (self, &info->error);
|
|
if (!port) {
|
|
/* If we can't get an AT port, but less specific registration checks
|
|
* were successful, just use that and don't return an error.
|
|
*/
|
|
if ( cur_cdma_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN
|
|
|| cur_evdo_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
|
|
g_clear_error (&info->error);
|
|
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 at least registered on the 1x network if we passed
|
|
* +CAD, +CSS, and QCDM Call Manager checking. But don't override a
|
|
* more specific registration state passed from a caller.
|
|
*/
|
|
if (cur_cdma_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
|
|
mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED);
|
|
|
|
/* Don't touch EVDO state; it's already either UNKNOWN, or been set
|
|
* by generic checking earlier.
|
|
*/
|
|
|
|
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 (g_error_matches (error, MM_MOBILE_ERROR, 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);
|
|
} else {
|
|
/* We're registered on the CDMA 1x network at least, but let subclasses
|
|
* do more specific registration checking.
|
|
*/
|
|
MM_GENERIC_CDMA_GET_CLASS (cdma)->query_registration_state (MM_GENERIC_CDMA (info->modem),
|
|
MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED,
|
|
MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
|
|
subclass_reg_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 the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
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),
|
|
MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
|
|
MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
|
|
subclass_reg_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_hdrstate_cb (MMQcdmSerialPort *port,
|
|
GByteArray *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = user_data;
|
|
QcdmResult *result = NULL;
|
|
guint32 sysmode;
|
|
MMModemCdmaRegistrationState cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
|
|
MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
|
|
MMAtSerialPort *at_port;
|
|
gboolean evdo_registered = FALSE;
|
|
|
|
if (error)
|
|
goto error;
|
|
|
|
sysmode = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "sysmode"));
|
|
|
|
/* Get HDR subsystem state to determine EVDO registration when in 1X mode */
|
|
result = qcdm_cmd_hdr_subsys_state_info_result ((const char *) response->data,
|
|
response->len,
|
|
NULL);
|
|
if (result) {
|
|
guint8 session_state = QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_CLOSED;
|
|
guint8 almp_state = QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_INACTIVE;
|
|
guint8 hybrid_mode = 0;
|
|
|
|
if ( qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE, &session_state)
|
|
&& qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE, &almp_state)
|
|
&& qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_HDR_HYBRID_MODE, &hybrid_mode)) {
|
|
|
|
/* EVDO state is registered if the HDR subsystem is registered, and
|
|
* we're in hybrid mode, and the Call Manager system mode is
|
|
* CDMA.
|
|
*/
|
|
if ( hybrid_mode
|
|
&& session_state == QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_OPEN
|
|
&& ( almp_state == QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_IDLE
|
|
|| almp_state == QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_CONNECTED))
|
|
evdo_registered = TRUE;
|
|
}
|
|
|
|
qcdm_result_unref (result);
|
|
}
|
|
|
|
switch (sysmode) {
|
|
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_CDMA:
|
|
cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
|
|
if (evdo_registered)
|
|
evdo_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),
|
|
cdma_state,
|
|
evdo_state,
|
|
subclass_reg_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
|
|
reg_cmstate_cb (MMQcdmSerialPort *port,
|
|
GByteArray *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = user_data;
|
|
MMAtSerialPort *at_port = NULL;
|
|
QcdmResult *result = NULL;
|
|
guint32 opmode = 0, sysmode = 0;
|
|
int err = QCDM_SUCCESS;
|
|
|
|
/* Parse the response */
|
|
if (!error)
|
|
result = qcdm_cmd_cm_subsys_state_info_result ((const char *) response->data, response->len, &err);
|
|
|
|
if (!result) {
|
|
/* If there was some error, fall back to use +CAD like we did before QCDM */
|
|
if (info->modem)
|
|
at_port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (info->modem), &info->error);
|
|
else {
|
|
g_set_error (&info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
|
|
"Failed to parse CM subsys state info command result: %d", err);
|
|
}
|
|
|
|
if (at_port)
|
|
mm_at_serial_port_queue_command (at_port, "+CAD?", 3, get_analog_digital_done, info);
|
|
else
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
|
|
qcdm_result_get_u32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &opmode);
|
|
qcdm_result_get_u32 (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) {
|
|
GByteArray *hdrstate;
|
|
|
|
mm_callback_info_set_data (info, "sysmode", GUINT_TO_POINTER (sysmode), NULL);
|
|
|
|
/* Get HDR subsystem state */
|
|
hdrstate = g_byte_array_sized_new (25);
|
|
hdrstate->len = qcdm_cmd_hdr_subsys_state_info_new ((char *) hdrstate->data, 25);
|
|
g_assert (hdrstate->len);
|
|
mm_qcdm_serial_port_queue_command (port, hdrstate, 3, reg_hdrstate_cb, info);
|
|
} 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);
|
|
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),
|
|
MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
|
|
MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
|
|
callback,
|
|
user_data);
|
|
|
|
port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error);
|
|
if (!port && !priv->qcdm) {
|
|
mm_dbg ("Returning saved registration states: 1x: %d EVDO: %d",
|
|
priv->cdma_1x_reg_state, priv->evdo_reg_state);
|
|
mm_generic_cdma_query_reg_state_set_callback_1x_state (info, priv->cdma_1x_reg_state);
|
|
mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, priv->evdo_reg_state);
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
g_clear_error (&info->error);
|
|
|
|
/* 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);
|
|
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);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
set_rm_proto_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
if (mm_callback_info_check_modem_removed (info) == FALSE) {
|
|
if (error)
|
|
info->error = g_error_copy (error);
|
|
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
}
|
|
|
|
static void
|
|
mm_generic_cdma_set_rm_protocol (MMGenericCdma *self,
|
|
RmProtocol proto,
|
|
MMModemFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info;
|
|
MMAtSerialPort *port;
|
|
char *cmd;
|
|
|
|
info = mm_callback_info_new (MM_MODEM (self), callback, user_data);
|
|
|
|
port = mm_generic_cdma_get_best_at_port (self, &info->error);
|
|
if (!port) {
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
g_clear_error (&info->error);
|
|
|
|
if (proto < RM_PROTO_ASYNC || proto > RM_PROTO_STU_III) {
|
|
g_set_error (&info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
|
|
"Invalid Rm interface protocol %d",
|
|
proto);
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
|
|
cmd = g_strdup_printf ("+CRM=%d", proto);
|
|
mm_at_serial_port_queue_command (port, cmd, 3, set_rm_proto_done, info);
|
|
g_free (cmd);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* MMModemSimple interface */
|
|
|
|
typedef enum {
|
|
SIMPLE_STATE_BEGIN = 0,
|
|
SIMPLE_STATE_ENABLE,
|
|
SIMPLE_STATE_REGISTER,
|
|
SIMPLE_STATE_PRE_CONNECT,
|
|
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_get_uint_property (MMCallbackInfo *info,
|
|
const char *name,
|
|
guint32 *out_val,
|
|
GError **error)
|
|
{
|
|
GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties");
|
|
GValue *value;
|
|
|
|
g_return_val_if_fail (out_val != NULL, FALSE);
|
|
|
|
value = (GValue *) g_hash_table_lookup (properties, name);
|
|
if (value) {
|
|
if (G_VALUE_HOLDS_UINT (value)) {
|
|
*out_val = g_value_get_uint (value);
|
|
return TRUE;
|
|
}
|
|
|
|
g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
|
|
"Invalid property type for '%s': %s (uint expected)",
|
|
name, G_VALUE_TYPE_NAME (value));
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
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);
|
|
g_error_free (error);
|
|
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;
|
|
MMGenericCdma *self;
|
|
MMGenericCdmaPrivate *priv;
|
|
SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state"));
|
|
const char *str;
|
|
guint id, rm_protocol = 0;
|
|
|
|
/* Do nothing if modem removed */
|
|
if (!modem || mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
goto out;
|
|
}
|
|
|
|
self = MM_GENERIC_CDMA (info->modem);
|
|
priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
|
|
|
|
switch (state) {
|
|
case SIMPLE_STATE_BEGIN:
|
|
/* Enable state */
|
|
state = set_simple_state (info, SIMPLE_STATE_ENABLE);
|
|
mm_modem_enable (modem, simple_state_machine, info);
|
|
break;
|
|
case SIMPLE_STATE_ENABLE:
|
|
/* Register state */
|
|
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:
|
|
/* Pre Connect state */
|
|
registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0);
|
|
state = set_simple_state (info, SIMPLE_STATE_PRE_CONNECT);
|
|
mm_modem_set_state (modem, MM_MODEM_STATE_REGISTERED, MM_MODEM_STATE_REASON_NONE);
|
|
|
|
/* Change the Rm interface protocol due to manager request if needed */
|
|
if (simple_get_uint_property (info, "rm-protocol", &rm_protocol, &info->error)) {
|
|
mm_generic_cdma_set_rm_protocol (self, rm_protocol, simple_state_machine, info);
|
|
break;
|
|
}
|
|
|
|
/* Or if the Rm protocol isn't the default, and there was no request
|
|
* to change it, do that now.
|
|
*/
|
|
if (priv->cur_crm != priv->orig_crm) {
|
|
mm_generic_cdma_set_rm_protocol (self, priv->orig_crm, simple_state_machine, info);
|
|
break;
|
|
}
|
|
|
|
/* Fall through */
|
|
case SIMPLE_STATE_PRE_CONNECT:
|
|
/* Connect state */
|
|
state = set_simple_state (info, SIMPLE_STATE_CONNECT);
|
|
str = simple_get_string_property (info, "number", &info->error);
|
|
mm_modem_connect (modem, str, simple_state_machine, info);
|
|
break;
|
|
case SIMPLE_STATE_CONNECT:
|
|
/* All done! */
|
|
state = set_simple_state (info, SIMPLE_STATE_DONE);
|
|
break;
|
|
case SIMPLE_STATE_DONE:
|
|
break;
|
|
}
|
|
|
|
out:
|
|
if (info->error || state == SIMPLE_STATE_DONE) {
|
|
if (modem)
|
|
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_clear_error (&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);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
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);
|
|
/* Kick one off immediately */
|
|
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->organize_ports = organize_ports;
|
|
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)
|
|
{
|
|
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (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);
|
|
|
|
/* Default to Network Layer Rm interface/PPP */
|
|
priv->orig_crm = priv->cur_crm = RM_PROTO_NETWORK_PPP;
|
|
}
|
|
|
|
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 MM_MODEM_CDMA_PROP_MEID:
|
|
g_value_set_string (value, priv->meid);
|
|
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 *generic_class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (generic_class);
|
|
MMModemBaseClass *base_class = MM_MODEM_BASE_CLASS (generic_class);
|
|
|
|
mm_generic_cdma_parent_class = g_type_class_peek_parent (generic_class);
|
|
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;
|
|
base_class->port_grabbed = port_grabbed;
|
|
generic_class->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_override_property (object_class,
|
|
MM_MODEM_CDMA_PROP_MEID,
|
|
MM_MODEM_CDMA_MEID);
|
|
|
|
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));
|
|
}
|
|
|