diff options
Diffstat (limited to 'src/mm-generic-cdma.c')
-rw-r--r-- | src/mm-generic-cdma.c | 1831 |
1 files changed, 1831 insertions, 0 deletions
diff --git a/src/mm-generic-cdma.c b/src/mm-generic-cdma.c new file mode 100644 index 0000000..50cd86c --- /dev/null +++ b/src/mm-generic-cdma.c @@ -0,0 +1,1831 @@ +/* -*- 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-serial-port.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-serial-parsers.h" + +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; + + 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; + + MMSerialPort *primary; + MMSerialPort *secondary; + MMPort *data; +} MMGenericCdmaPrivate; + +enum { + PROP_0, + PROP_EVDO_REV0, + PROP_EVDO_REVA, + PROP_REG_TRY_CSS, + LAST_PROP +}; + +MMModem * +mm_generic_cdma_new (const char *device, + const char *driver, + const char *plugin, + gboolean evdo_rev0, + gboolean evdo_revA) +{ + g_return_val_if_fail (device != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + g_return_val_if_fail (plugin != NULL, NULL); + + return MM_MODEM (g_object_new (MM_TYPE_GENERIC_CDMA, + MM_MODEM_MASTER_DEVICE, device, + MM_MODEM_DRIVER, driver, + MM_MODEM_PLUGIN, plugin, + MM_GENERIC_CDMA_EVDO_REV0, evdo_rev0, + MM_GENERIC_CDMA_EVDO_REVA, evdo_revA, + NULL)); +} + +/*****************************************************************************/ + +static void +check_valid (MMGenericCdma *self) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + gboolean new_valid = FALSE; + + if (priv->primary && priv->data) + new_valid = TRUE; + + mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid); +} + +static gboolean +owns_port (MMModem *modem, const char *subsys, const char *name) +{ + return !!mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name); +} + +MMPort * +mm_generic_cdma_grab_port (MMGenericCdma *self, + const char *subsys, + const char *name, + MMPortType suggested_type, + gpointer user_data, + GError **error) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + MMPortType ptype = MM_PORT_TYPE_IGNORED; + MMPort *port; + + g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), FALSE); + if (priv->primary) + g_return_val_if_fail (suggested_type != MM_PORT_TYPE_PRIMARY, FALSE); + + if (!strcmp (subsys, "tty")) { + if (suggested_type != MM_PORT_TYPE_UNKNOWN) + ptype = suggested_type; + else { + if (!priv->primary) + ptype = MM_PORT_TYPE_PRIMARY; + else if (!priv->secondary) + ptype = MM_PORT_TYPE_SECONDARY; + } + } + + port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype); + if (port && MM_IS_SERIAL_PORT (port)) { + g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL); + mm_serial_port_set_response_parser (MM_SERIAL_PORT (port), + mm_serial_parser_v1_parse, + mm_serial_parser_v1_new (), + mm_serial_parser_v1_destroy); + + if (ptype == MM_PORT_TYPE_PRIMARY) { + priv->primary = MM_SERIAL_PORT (port); + if (!priv->data) { + priv->data = port; + g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); + } + check_valid (self); + } else if (ptype == MM_PORT_TYPE_SECONDARY) + priv->secondary = MM_SERIAL_PORT (port); + } else { + /* Net device (if any) is the preferred data port */ + if (!priv->data || MM_IS_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 == MM_PORT (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 == MM_PORT (priv->secondary)) { + mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); + priv->secondary = NULL; + } + + check_valid (MM_GENERIC_CDMA (modem)); +} + +MMSerialPort * +mm_generic_cdma_get_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; +} + +/*****************************************************************************/ + +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 +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 +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 { + /* Open up the second port, if one exists */ + if (priv->secondary) { + if (!mm_serial_port_open (priv->secondary, &info->error)) { + g_assert (info->error); + goto out; + } + } + + update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE); + } + +out: + if (info->error) { + mm_modem_set_state (MM_MODEM (info->modem), + MM_MODEM_STATE_DISABLED, + MM_MODEM_STATE_REASON_NONE); + } + + mm_callback_info_schedule (info); +} + +static void +enable_error_reporting_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); + + /* Just ignore errors, see comment in init_done() */ + if (error) + g_warning ("Your CDMA modem does not support +CMEE command"); + + if (MM_GENERIC_CDMA_GET_CLASS (self)->post_enable) + MM_GENERIC_CDMA_GET_CLASS (self)->post_enable (self, enable_all_done, info); + else + enable_all_done (MM_MODEM (self), NULL, info); +} + +static void +init_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) { + mm_modem_set_state (MM_MODEM (info->modem), + MM_MODEM_STATE_DISABLED, + MM_MODEM_STATE_REASON_NONE); + + info->error = g_error_copy (error); + mm_callback_info_schedule (info); + } else { + /* Try to enable better error reporting. My experience so far indicates + there's some CDMA modems that does not support that. + FIXME: It's mandatory by spec, so it really shouldn't be optional. Figure + out which CDMA modems have problems with it and implement plugin for them. + */ + mm_serial_port_queue_command (port, "+CMEE=1", 3, enable_error_reporting_done, user_data); + } +} + +static void +flash_done (MMSerialPort *port, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) { + mm_modem_set_state (MM_MODEM (info->modem), + MM_MODEM_STATE_DISABLED, + MM_MODEM_STATE_REASON_NONE); + + /* Flash failed for some reason */ + info->error = g_error_copy (error); + mm_callback_info_schedule (info); + return; + } + + mm_serial_port_queue_command (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 (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 (priv->primary, 100, flash_done, info); +} + +static void +disable_set_previous_state (MMModem *modem, MMCallbackInfo *info) +{ + MMModemState prev_state; + + /* Reset old state since the operation failed */ + prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_CDMA_PREV_STATE_TAG)); + mm_modem_set_state (modem, prev_state, MM_MODEM_STATE_REASON_NONE); +} + +static void +disable_all_done (MMModem *modem, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + info->error = mm_modem_check_removed (modem, error); + if (info->error) { + if (modem) + disable_set_previous_state (modem, info); + } else { + MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + + mm_serial_port_close (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); + + if (priv->secondary) + mm_serial_port_close (priv->secondary); + + 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 (priv->primary, 1000, disable_flash_done, info); + else + disable_flash_done (priv->primary, NULL, info); +} + +static void +dial_done (MMSerialPort *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_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 (priv->primary, 1000, disconnect_flash_done, info); +} + +static void +card_info_invoke (MMCallbackInfo *info) +{ + MMModemInfoFn callback = (MMModemInfoFn) info->callback; + + callback (info->modem, + (char *) mm_callback_info_get_data (info, "card-info-manufacturer"), + (char *) mm_callback_info_get_data (info, "card-info-model"), + (char *) mm_callback_info_get_data (info, "card-info-version"), + info->error, info->user_data); +} + +static const char * +strip_response (const char *resp, const char *cmd) +{ + const char *p = resp; + + if (p) { + if (!strncmp (p, cmd, strlen (cmd))) + p += strlen (cmd); + while (*p == ' ') + p++; + } + return p; +} + +static void +get_version_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *p; + + if (!error) { + p = strip_response (response->str, "+GMR:"); + mm_callback_info_set_data (info, "card-info-version", g_strdup (p), g_free); + } else if (!info->error) + info->error = g_error_copy (error); + + mm_callback_info_schedule (info); +} + +static void +get_model_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *p; + + if (!error) { + p = strip_response (response->str, "+GMM:"); + mm_callback_info_set_data (info, "card-info-model", g_strdup (p), g_free); + } else if (!info->error) + info->error = g_error_copy (error); +} + +static void +get_manufacturer_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *p; + + if (!error) { + p = strip_response (response->str, "+GMI:"); + mm_callback_info_set_data (info, "card-info-manufacturer", g_strdup (p), g_free); + } else + info->error = g_error_copy (error); +} + +static void +get_card_info (MMModem *modem, + MMModemInfoFn callback, + gpointer user_data) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + MMCallbackInfo *info; + MMSerialPort *port = priv->primary; + + info = mm_callback_info_new_full (MM_MODEM (modem), + card_info_invoke, + G_CALLBACK (callback), + user_data); + + if (mm_port_get_connected (MM_PORT (priv->primary))) { + if (!priv->secondary) { + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, + "Cannot modem info while connected"); + mm_callback_info_schedule (info); + return; + } + + /* Use secondary port if primary is connected */ + port = priv->secondary; + } + + mm_serial_port_queue_command_cached (port, "+GMI", 3, get_manufacturer_done, info); + mm_serial_port_queue_command_cached (port, "+GMM", 3, get_model_done, info); + mm_serial_port_queue_command_cached (port, "+GMR", 3, get_version_done, info); +} + +/*****************************************************************************/ + +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 (MMSerialPort *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_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 +get_signal_quality (MMModemCdma *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + MMCallbackInfo *info; + MMSerialPort *port = priv->primary; + + if (mm_port_get_connected (MM_PORT (priv->primary))) { + if (!priv->secondary) { + g_message ("Returning saved signal quality %d", priv->cdma1x_quality); + callback (MM_MODEM (modem), priv->cdma1x_quality, NULL, user_data); + return; + } + + /* Use secondary port if primary is connected */ + port = priv->secondary; + } + + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + mm_serial_port_queue_command (port, "+CSQ", 3, get_signal_quality_done, info); +} + +static void +get_string_done (MMSerialPort *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 = strip_response (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) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + MMCallbackInfo *info; + GError *error; + MMSerialPort *port = priv->primary; + + if (mm_port_get_connected (MM_PORT (priv->primary))) { + if (!priv->secondary) { + error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, + "Cannot get ESN while connected"); + callback (MM_MODEM (modem), NULL, error, user_data); + g_error_free (error); + return; + } + + /* Use secondary port if primary is connected */ + port = priv->secondary; + } + + info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); + mm_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 void +serving_system_done (MMSerialPort *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) { + /* NOTE: update reg_state_css_response() if this error changes */ + info->error = g_error_new_literal (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_NO_NETWORK, + "No service"); + } 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 +get_serving_system (MMModemCdma *modem, + MMModemCdmaServingSystemFn callback, + gpointer user_data) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + MMCallbackInfo *info; + GError *error; + MMSerialPort *port = priv->primary; + + if (mm_port_get_connected (MM_PORT (priv->primary))) { + if (!priv->secondary) { + error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, + "Cannot get serving system while connected"); + callback (modem, 0, 0, 0, error, user_data); + g_error_free (error); + return; + } + + /* Use secondary port if primary is connected */ + port = priv->secondary; + } + + info = mm_callback_info_new_full (MM_MODEM (modem), + serving_system_invoke, + G_CALLBACK (callback), + user_data); + + mm_serial_port_queue_command (port, "+CSS?", 3, serving_system_done, info); +} + +#define CDMA_1X_STATE_TAG "cdma-1x-reg-state" +#define EVDO_STATE_TAG "evdo-reg-state" + +void +mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info, + MMModemCdmaRegistrationState new_state) +{ + g_return_if_fail (info != NULL); + g_return_if_fail (info->modem != NULL); + g_return_if_fail (MM_IS_GENERIC_CDMA (info->modem)); + + mm_callback_info_set_data (info, CDMA_1X_STATE_TAG, GUINT_TO_POINTER (new_state), NULL); +} + +void +mm_generic_cdma_query_reg_state_set_callback_evdo_state (MMCallbackInfo *info, + MMModemCdmaRegistrationState new_state) +{ + g_return_if_fail (info != NULL); + g_return_if_fail (info->modem != NULL); + g_return_if_fail (MM_IS_GENERIC_CDMA (info->modem)); + + mm_callback_info_set_data (info, EVDO_STATE_TAG, GUINT_TO_POINTER (new_state), NULL); +} + +static void +registration_state_invoke (MMCallbackInfo *info) +{ + MMModemCdmaRegistrationStateFn callback = (MMModemCdmaRegistrationStateFn) info->callback; + + /* note: This is the MMModemCdma interface callback */ + callback (MM_MODEM_CDMA (info->modem), + GPOINTER_TO_UINT (mm_callback_info_get_data (info, CDMA_1X_STATE_TAG)), + GPOINTER_TO_UINT (mm_callback_info_get_data (info, EVDO_STATE_TAG)), + info->error, + info->user_data); +} + +MMCallbackInfo * +mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self, + MMModemCdmaRegistrationStateFn callback, + gpointer user_data) +{ + MMGenericCdmaPrivate *priv; + MMCallbackInfo *info; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), NULL); + g_return_val_if_fail (callback != NULL, NULL); + + info = mm_callback_info_new_full (MM_MODEM (self), + registration_state_invoke, + G_CALLBACK (callback), + user_data); + + /* Fill with current state */ + priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + mm_callback_info_set_data (info, + CDMA_1X_STATE_TAG, + GUINT_TO_POINTER (priv->cdma_1x_reg_state), + NULL); + mm_callback_info_set_data (info, + EVDO_STATE_TAG, + GUINT_TO_POINTER (priv->evdo_reg_state), + NULL); + return info; +} + +static void +set_callback_1x_state_helper (MMCallbackInfo *info, + MMModemCdmaRegistrationState new_state) +{ + 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) +{ + MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); + + mm_generic_cdma_set_evdo_registration_state (self, new_state); + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, priv->evdo_reg_state); +} + +static void +reg_state_query_done (MMModemCdma *cdma, + MMModemCdmaRegistrationState cdma_1x_reg_state, + MMModemCdmaRegistrationState evdo_reg_state, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + else { + set_callback_1x_state_helper (info, cdma_1x_reg_state); + set_callback_evdo_state_helper (info, evdo_reg_state); + } + + mm_callback_info_schedule (info); +} + +static void +query_subclass_registration_state (MMGenericCdma *self, MMCallbackInfo *info) +{ + /* Let subclasses figure out roaming and detailed registration state */ + if (MM_GENERIC_CDMA_GET_CLASS (self)->query_registration_state) { + MM_GENERIC_CDMA_GET_CLASS (self)->query_registration_state (self, + reg_state_query_done, + info); + } else { + /* Or if the subclass doesn't implement more specific checking, + * assume we're registered. + */ + reg_state_query_done (MM_MODEM_CDMA (self), + MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED, + MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED, + NULL, + info); + } +} + +static void +reg_state_css_response (MMModemCdma *cdma, + guint32 class, + unsigned char band, + guint32 sid, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + /* We'll get an error if the SID isn't valid, so detect that and + * report unknown registration state. + */ + if (error) { + if ( (error->domain == MM_MOBILE_ERROR) + && (error->code == MM_MOBILE_ERROR_NO_NETWORK)) { + set_callback_1x_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + set_callback_evdo_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + } else { + /* Some other error parsing CSS results */ + info->error = g_error_copy (error); + } + mm_callback_info_schedule (info); + return; + } + + query_subclass_registration_state (MM_GENERIC_CDMA (info->modem), info); +} + +static void +get_analog_digital_done (MMSerialPort *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 = strip_response (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. + */ + query_subclass_registration_state (MM_GENERIC_CDMA (info->modem), 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 +get_registration_state (MMModemCdma *modem, + MMModemCdmaRegistrationStateFn callback, + gpointer user_data) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + MMCallbackInfo *info; + MMSerialPort *port = priv->primary; + + if (mm_port_get_connected (MM_PORT (priv->primary))) { + if (!priv->secondary) { + g_message ("Returning saved registration states: 1x: %d EVDO: %d", + priv->cdma_1x_reg_state, priv->evdo_reg_state); + callback (MM_MODEM_CDMA (modem), priv->cdma_1x_reg_state, priv->evdo_reg_state, NULL, user_data); + return; + } + + /* Use secondary port if primary is connected */ + port = priv->secondary; + } + + info = mm_generic_cdma_query_reg_state_callback_info_new (MM_GENERIC_CDMA (modem), callback, user_data); + mm_serial_port_queue_command (port, "+CAD?", 3, get_analog_digital_done, info); +} + +/*****************************************************************************/ +/* MMModemSimple interface */ + +typedef enum { + SIMPLE_STATE_BEGIN = 0, + SIMPLE_STATE_ENABLE, + SIMPLE_STATE_REGISTER, + SIMPLE_STATE_CONNECT, + SIMPLE_STATE_DONE +} SimpleState; + +static const char * +simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error) +{ + GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties"); + GValue *value; + + value = (GValue *) g_hash_table_lookup (properties, name); + if (!value) + return NULL; + + if (G_VALUE_HOLDS_STRING (value)) + return g_value_get_string (value); + + g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Invalid property type for '%s': %s (string expected)", + name, G_VALUE_TYPE_NAME (value)); + + return NULL; +} + +static gboolean +simple_reg_retry (gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (info->modem), + simple_reg_callback, + info); + return TRUE; +} + +static void +simple_reg_callback (MMModemCdma *modem, + MMModemCdmaRegistrationState cdma_1x_reg_state, + MMModemCdmaRegistrationState evdo_reg_state, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + gboolean no_service_error = FALSE; + + if ( error + && (error->domain == MM_MOBILE_ERROR) + && (error->code == MM_MOBILE_ERROR_NO_NETWORK)) + no_service_error = TRUE; + + /* Fail immediately on anything but "no service" */ + if (error && !no_service_error) { + simple_state_machine (MM_MODEM (modem), error, info); + return; + } + + if ( no_service_error + || ( (cdma_1x_reg_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) + && (evdo_reg_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN))) { + /* Not registered yet, queue up a retry */ + priv->reg_tries++; + if (priv->reg_tries > 15) { + error = g_error_new_literal (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_NO_NETWORK, + "No service"); + simple_state_machine (MM_MODEM (modem), error, info); + g_error_free (error); + return; + } + + /* otherwise, just try again in a bit */ + if (!priv->reg_retry_id) + priv->reg_retry_id = g_timeout_add_seconds (4, simple_reg_retry, info); + } else { + /* Yay, at least one of 1x or EVDO is registered, we can proceed to dial */ + simple_state_machine (MM_MODEM (modem), NULL, info); + } +} + +static void +reg_state_changed (MMModemCdma *self, + MMModemCdmaRegistrationState cdma_1x_new_state, + MMModemCdmaRegistrationState evdo_new_state, + gpointer user_data) +{ +/* Disabled for now... changing the registration state from the + * subclass' query_registration_state handler also emits the registration + * state changed signal, which will call this function, and execute + * simple_state_machine() to advance to the next state. Then however + * query_registration_state will call its callback, which ends up in + * simple_reg_callback(), which calls simple_state_machine() too in + * the same mainloop iteration. Not good. So until that's sorted out + * we'll just have to poll registration state (every 4 seconds so its + * not that bad. + */ +#if 0 + MMCallbackInfo *info = user_data; + + /* If we're registered, we can proceed */ + if ( (cdma_1x_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) + || (evdo_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)) + simple_state_machine (MM_MODEM (modem), NULL, info); +#endif +} + +static SimpleState +set_simple_state (MMCallbackInfo *info, SimpleState state) +{ + mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (state), NULL); + return state; +} + +static void +simple_state_machine (MMModem *modem, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); + SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state")); + const char *str; + guint id; + + if (error) { + info->error = g_error_copy (error); + goto out; + } + + switch (state) { + case SIMPLE_STATE_BEGIN: + state = set_simple_state (info, SIMPLE_STATE_ENABLE); + mm_modem_enable (modem, simple_state_machine, info); + break; + case SIMPLE_STATE_ENABLE: + state = set_simple_state (info, SIMPLE_STATE_REGISTER); + mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (modem), + simple_reg_callback, + info); + id = g_signal_connect (modem, + MM_MODEM_CDMA_REGISTRATION_STATE_CHANGED, + G_CALLBACK (reg_state_changed), + info); + priv->reg_state_changed_id = id; + break; + case SIMPLE_STATE_REGISTER: + registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0); + state = set_simple_state (info, SIMPLE_STATE_CONNECT); + mm_modem_set_state (modem, MM_MODEM_STATE_REGISTERED, MM_MODEM_STATE_REASON_NONE); + + str = simple_get_string_property (info, "number", &info->error); + mm_modem_connect (modem, str, simple_state_machine, info); + break; + case SIMPLE_STATE_CONNECT: + state = set_simple_state (info, SIMPLE_STATE_DONE); + break; + case SIMPLE_STATE_DONE: + break; + } + + out: + if (info->error || state == SIMPLE_STATE_DONE) { + registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0); + mm_callback_info_schedule (info); + } +} + +static void +simple_connect (MMModemSimple *simple, + GHashTable *properties, + MMModemFn callback, + gpointer user_data) +{ + MMGenericCdma *self = MM_GENERIC_CDMA (simple); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + MMCallbackInfo *info; + GError *error = NULL; + + if (priv->simple_connect_info) { + error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_OPERATION_IN_PROGRESS, + "Connection is already in progress"); + callback (MM_MODEM (simple), error, user_data); + g_error_free (error); + return; + } + + info = mm_callback_info_new (MM_MODEM (simple), callback, user_data); + priv->simple_connect_info = info; + mm_callback_info_set_data (info, "simple-connect-properties", + g_hash_table_ref (properties), + (GDestroyNotify) g_hash_table_unref); + + /* At least number must be present */ + if (!simple_get_string_property (info, "number", &error)) { + if (!error) + error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Missing number property"); + } + + simple_state_machine (MM_MODEM (simple), error, info); +} + +static void +simple_free_gvalue (gpointer data) +{ + g_value_unset ((GValue *) data); + g_slice_free (GValue, data); +} + +static GValue * +simple_uint_value (guint32 i) +{ + GValue *val; + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_UINT); + g_value_set_uint (val, i); + + return val; +} + +static void +simple_status_got_signal_quality (MMModem *modem, + guint32 result, + GError *error, + gpointer user_data) +{ + if (error) + g_warning ("Error getting signal quality: %s", error->message); + else + g_hash_table_insert ((GHashTable *) user_data, "signal_quality", simple_uint_value (result)); +} + +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, "simple-get-status"), + 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, g_free, simple_free_gvalue); + mm_callback_info_set_data (info, "simple-get-status", properties, (GDestroyNotify) g_hash_table_unref); + mm_modem_cdma_get_signal_quality (MM_MODEM_CDMA (simple), simple_status_got_signal_quality, properties); +} + +/*****************************************************************************/ + +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_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 GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + + object = G_OBJECT_CLASS (mm_generic_cdma_parent_class)->constructor (type, + n_construct_params, + construct_params); + if (object) { + g_signal_connect (object, "notify::" MM_MODEM_VALID, + G_CALLBACK (modem_valid_changed), NULL); + } + + return object; +} + +static void +mm_generic_cdma_init (MMGenericCdma *self) +{ +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (object); + + switch (prop_id) { + case MM_MODEM_PROP_TYPE: + break; + case PROP_EVDO_REV0: + priv->evdo_rev0 = g_value_get_boolean (value); + break; + case PROP_EVDO_REVA: + priv->evdo_revA = g_value_get_boolean (value); + break; + case PROP_REG_TRY_CSS: + priv->reg_try_css = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (object); + + switch (prop_id) { + case MM_MODEM_PROP_DATA_DEVICE: + if (priv->data) + g_value_set_string (value, mm_port_get_device (priv->data)); + else + g_value_set_string (value, NULL); + break; + case MM_MODEM_PROP_TYPE: + g_value_set_uint (value, MM_MODEM_TYPE_CDMA); + break; + case PROP_EVDO_REV0: + g_value_set_boolean (value, priv->evdo_rev0); + break; + case PROP_EVDO_REVA: + g_value_set_boolean (value, priv->evdo_revA); + break; + case PROP_REG_TRY_CSS: + g_value_set_boolean (value, priv->reg_try_css); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + registration_cleanup (MM_GENERIC_CDMA (object), MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL); + + G_OBJECT_CLASS (mm_generic_cdma_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + G_OBJECT_CLASS (mm_generic_cdma_parent_class)->finalize (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; + object_class->finalize = finalize; + object_class->constructor = constructor; + + /* Properties */ + g_object_class_override_property (object_class, + MM_MODEM_PROP_DATA_DEVICE, + MM_MODEM_DATA_DEVICE); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_TYPE, + MM_MODEM_TYPE); + + g_object_class_install_property (object_class, PROP_EVDO_REV0, + g_param_spec_boolean (MM_GENERIC_CDMA_EVDO_REV0, + "EVDO rev0", + "Supports EVDO rev0", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_EVDO_REVA, + g_param_spec_boolean (MM_GENERIC_CDMA_EVDO_REVA, + "EVDO revA", + "Supports EVDO revA", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_REG_TRY_CSS, + g_param_spec_boolean (MM_GENERIC_CDMA_REGISTRATION_TRY_CSS, + "RegistrationTryCss", + "Use Serving System response when checking modem" + " registration state.", + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + |