Files
ModemManager/src/mm-generic-cdma.c
T
Dan Williams 794353ebcc cdma: update reg state to HOME based on SPERI response
If the SPERI response indicates the home network, then set
CDMA registration to HOME if it was REGISTERED (which is
less specific).
2011-01-05 11:15:11 -06:00

2373 lines
81 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;
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;
} 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);
}
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) {
g_warn_if_fail (port != NULL);
return NULL;
}
if (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);
}
/* Get the modem's general info */
initial_info_check (self);
/* Get modem's ESN number */
initial_esn_check (self);
} else if (ptype == MM_PORT_TYPE_SECONDARY)
priv->secondary = MM_AT_SERIAL_PORT (port);
} else if (MM_IS_QCDM_SERIAL_PORT (port)) {
if (!priv->qcdm)
priv->qcdm = MM_QCDM_SERIAL_PORT (port);
} else if (!strcmp (subsys, "net")) {
/* 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;
}
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
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
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 {
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;
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_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;
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_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;
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, 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;
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);
}
/*****************************************************************************/
/* 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;
info->error = mm_modem_check_removed (info->modem, error);
if (!info->error) {
/* 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 (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 (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 registered on the 1x network if we passed +CAD, +CSS,
* and QCDM Call Manager checking.
*/
mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED);
mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
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 (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_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE, &session_state)
&& qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE, &almp_state)
&& qcdm_result_get_uint8 (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;
GError *qcdm_error = NULL;
/* Parse the response */
if (!error)
result = qcdm_cmd_cm_subsys_state_info_result ((const char *) response->data, response->len, &qcdm_error);
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
info->error = g_error_copy (qcdm_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);
g_clear_error (&qcdm_error);
return;
}
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) {
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, NULL);
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) {
g_message ("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, 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;
info->error = mm_modem_check_removed (modem, error);
if (info->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) {
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_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);
/* 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->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 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 *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_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));
}