blob: 05c66b069d5298e7c176fa044c5805c07a100123 [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (C) 2010-2011 The Chromium OS Authors. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <gdbus.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define CONNMAN_API_SUBJECT_TO_CHANGE
#include <connman/assert.h>
#include <connman/element.h>
#include <connman/dbus.h>
#include <connman/device.h>
#include <connman/network.h>
#include <connman/inet.h>
#include <connman/log.h>
#include <connman/option.h>
#include <connman/plugin.h>
#define _DBG_MODEMMGR(fmt, arg...) DBG(DBG_MODEM, fmt, ## arg)
#include <mm/mm-modem.h>
#include "mobile_provider.h"
#define DBUS_METHOD_GETALL "GetAll"
#define DEFAULT_TIMEOUT_MS 5000
#define CONNECT_TIMEOUT_MS 45000
#define ACTIVATE_TIMEOUT_MS 120000
#define NETWORK_SCAN_TIMEOUT_MS 120000
#define NETWORK_REGISTER_TIMEOUT_MS 90000
#define MM_MODEM_ERROR(error) MM_MODEM_INTERFACE "." error
#define MM_MOBILE_ERROR(error) MM_MODEM_GSM_INTERFACE "." error
static const char *kErrorIncorrectPassword =
MM_MOBILE_ERROR(MM_ERROR_MODEM_GSM_INCORRECTPASSWORD);
static const char *kErrorSimPinRequired =
MM_MOBILE_ERROR(MM_ERROR_MODEM_GSM_SIMPINREQUIRED);
static const char *kErrorSimPukRequired =
MM_MOBILE_ERROR( MM_ERROR_MODEM_GSM_SIMPUKREQUIRED);
static const char *kErrorGprsNotSubscribed =
MM_MOBILE_ERROR(MM_ERROR_MODEM_GSM_GPRSNOTSUBSCRIBED);
static const char *kErrorConnected =
MM_MODEM_ERROR(MM_ERROR_MODEM_CONNECTED);
#define CROMO_ERROR(error) "org.chromium.ModemManager.Error." error
static const char *kErrorOperationInitiated =
CROMO_ERROR("OperationInitiated");
/* Name of the network */
#define NETWORK_NAME "CellularNetwork"
#define IP_METHOD_NONE (-1)
/*
* For testing the mechanism for limiting connect
* retries, change this to a small number.
*/
#define MAX_CONNECT_TRIES (9999)
/*
* from mm-modem.h in ModemManager. This enum
* should move into an XML file to become part
* of the DBus API.
*/
typedef enum {
MM_MODEM_STATE_UNKNOWN = 0,
MM_MODEM_STATE_DISABLED = 10,
MM_MODEM_STATE_DISABLING = 20,
MM_MODEM_STATE_ENABLING = 30,
MM_MODEM_STATE_ENABLED = 40,
MM_MODEM_STATE_SEARCHING = 50,
MM_MODEM_STATE_REGISTERED = 60,
MM_MODEM_STATE_DISCONNECTING = 70,
MM_MODEM_STATE_CONNECTING = 80,
MM_MODEM_STATE_CONNECTED = 90,
MM_MODEM_STATE_LAST = MM_MODEM_STATE_CONNECTED
} MMModemState;
enum {
// Sugar to make calls to modem_connect_failed clearer
MM_NO_RETRY = FALSE,
MM_RETRY = TRUE,
};
/* Modem states */
enum modem_state {
/**
* MODEM_STATE_UNINITIALIZED - Modem recently appeared
*
* Try to read state of modem from ModemManager
*/
MODEM_STATE_UNINITIALIZED,
/**
* MODEM_STATE_DISABLED - Radio Off
*
* This state indicates that the modem radio is not turned on.
* This is the initial state off the modem.
*/
MODEM_STATE_DISABLED,
/**
* MODEM_STATE_ENABLED - Modem enabled, radio on
*
* This state indicates that the modem radio is turned on, and
* it should be possible to measure signal strength
*/
MODEM_STATE_ENABLED,
/**
* MODEM_STATE_REGISTERED
*
* This modem has registered with a network, has signal
* quality measurements.
*/
MODEM_STATE_REGISTERED,
/**
* MODEM_STATE_CONNECTED
*
* This modem has connected to a network.
*/
MODEM_STATE_CONNECTED,
/**
* MODEM_STATE_FAILED
*
* A terminal state. Failed to read critical information from
* the modem.
*/
MODEM_STATE_FAILED,
/**
* MODEM_STATE_REMOVED
*
* A terminal state. Device is removed. Fail all operations.
*/
MODEM_STATE_REMOVED,
};
enum modem_event {
ME_NO_EVENT,
ME_PROPERTIES_CHANGED,
ME_SIGNAL_QUALITY_CHANGED,
ME_REGISTRATION_STATE_CHANGED,
ME_ENABLE_SUCCEEDED,
ME_ENABLE_FAILED,
ME_REGISTER_SUCCEEDED,
ME_REGISTER_FAILED,
ME_DISABLE_SUCCEEDED,
ME_DISABLE_FAILED,
ME_GETIP4CONFIG_SUCCEEDED,
ME_GETIP4CONFIG_FAILED,
ME_GETINFO_SUCCEEDED,
ME_GETINFO_FAILED,
ME_GET_PROPERTIES_SUCCEEDED,
ME_GET_PROPERTIES_FAILED,
ME_DISCONNECT_SUCCEEDED,
ME_DISCONNECT_FAILED,
ME_GET_SIGNALQUALITY_SUCCEEDED,
ME_GET_SIGNALQUALITY_FAILED,
ME_GET_SERVING_SYSTEM_SUCCEEDED,
ME_GET_SERVING_SYSTEM_FAILED,
ME_GET_REGISTRATION_STATE_SUCCEEDED,
ME_GET_REGISTRATION_STATE_FAILED,
ME_CREATE_DEVICE_FAILED,
};
enum apn_try_state {
APN_TRY_START,
APN_TRY_SAVED,
APN_TRY_USER_APN,
APN_TRY_LIST,
APN_TRY_NULL_APN,
APN_TRY_FAILED
};
static const char* kDefaultMMSearchPath =
MM_MODEMMANAGER_SERVICE ":" MM_MODEMMANAGER_PATH
",org.chromium.ModemManager:/org/chromium/ModemManager";
struct modem_data;
static void modem_handle_event(struct modem_data *modem, enum modem_event event);
static void handle_reported_connect(struct modem_data *modem);
static void handle_reported_disconnect(struct modem_data *modem);
static const char *eventToString(enum modem_event);
static const char *stateToString(enum modem_state);
static enum connman_network_activation_state convert_activation_state(
guint mm_activation_state);
static enum connman_element_error convert_activation_error(
guint mm_activation_error);
typedef void (*modem_function)(struct modem_data *modem);
typedef enum connman_element_error (*error_conversion_func)(const DBusError *);
static int modem_connect_failed(struct modem_data *modem, connman_bool_t retry);
static int modem_set_connected(struct modem_data *modem, gboolean connected);
struct modem_method_callback {
struct connman_dbus_method_callback *cb_data;
struct modem_data *modem;
error_conversion_func convert_error;
};
/*
* A modem task contains the state necessary to move through a minor
* state machine. It is initialized with a list of functions that
* need to be called in order to effect a transition.
*
* Tasks must do one of the following:
* send a DBUS message whose reply will later call modem_handle_event
* immediately call modem_handle_event()
*
* A modem object can only be running a single task at a time.
*/
struct modem_task_work {
const char *name;
modem_function functions[];
};
struct modem_task {
gboolean remove_on_complete; /* modem removed while task running */
int index; /* state within this state machine */
struct modem_task_work *work;
};
struct modem_data {
char *owner; /* unique bus name of service owning this modem */
char *service; /* name of the service owning this modem */
char *dbus_path;
GSList *pending_dbus_calls;
struct connman_device *cm_device;
/* Modem connection profile information */
char *phone_number; /* from a profile */
gint type; /* MM_MODEM_TYPE_xxx */
char *meid;
char *imei;
char *imsi;
enum modem_state state; /* modem state */
int scangen; /* fake scan generation number */
unsigned int signal_strength; /* most recently reported */
struct {
guint evdo_registration_state;
guint _1x_registration_state; /* 1xRTT network reg state */
guint activation_state;
} cdma;
struct {
struct mobile_provider *provider; /* Provider from the SIM */
gint registration_state; /* network registration state */
char *network_id; /* operator MCC/MNC as string */
char *operator_name; /* operator name reported in */
/* registration info */
char *operator_country; /* operator country from same */
char *spn; /* service provider name from SIM */
struct {
gboolean enabled; /* SIM PIN lock is enabled */
char *unlock_required; /* modem needs to be unlocked? */
guint retries; /* # unlock retries allowed */
} lock;
gint apn_index; /* next APN in list to try */
guint connect_tries;
enum apn_try_state apn_try_state;
struct connman_network_apn next_apn;
gint id_index;
} gsm;
enum connman_network_cellular_technology
network_technology; /* EDGE, UMTS, etc. */
struct {
char *url; /* online payment url */
char *method; /* online payment method */
char *postdata; /* online payment postdata */
} olp;
char *usage_url; /* data usage url */
char *device;
char *driver;
char *master_device;
char *network_dbus_name;
char *configured_carrier; /* carrier for which modem is configured */
gint ip_method; /* MM_MODEM_IP_METHOD_xxx */
gboolean pending_task; /* Is there a task pending? */
gboolean disconnect_needed; /* Disconnect before removing modem? */
gboolean connect_needed; /* modem manager reports connected at startup */
struct modem_task task; /* minor state machine */
};
struct modem_manager {
int watch; /* dbus watch */
char *service; /* service name for modem manager */
char *path; /* path for ModemManager Interface */
char *bus_name; /* unique bus name for the service */
};
gboolean imei_needed(struct modem_data *);
gboolean imsi_needed(struct modem_data *);
gboolean spn_needed(struct modem_data *);
gboolean msisdn_needed(struct modem_data *);
struct gsm_id_request {
const char *name;
const char *method;
gboolean (*id_needed)(struct modem_data *);
} gsm_id_requests[] = {
{"imei", MM_MODEM_GSM_CARD_METHOD_GETIMEI, imei_needed},
{"imsi", MM_MODEM_GSM_CARD_METHOD_GETIMSI, imsi_needed},
{"spn", MM_MODEM_GSM_CARD_METHOD_GETSPN, spn_needed},
{"msisdn", MM_MODEM_GSM_CARD_METHOD_GETMSISDN, msisdn_needed},
{NULL}
};
static struct mobile_provider_db *provider_db = NULL;
static GHashTable *modem_manager_watches = NULL;
static GHashTable *modem_hash = NULL;
static void modem_task_completed(struct modem_data **m)
{
struct modem_data *modem = *m;
if (modem->pending_task) {
_DBG_MODEMMGR("%s: %s",
modem->dbus_path, modem->task.work->name);
} else {
_DBG_MODEMMGR("with no pending task");
}
modem->pending_task = FALSE;
if (modem->task.remove_on_complete) {
modem->state = MODEM_STATE_FAILED;
g_hash_table_remove(modem_hash, modem->dbus_path);
*m = NULL;
}
};
/**
* Run the next function in the task
*/
static void modem_task_run(struct modem_data *modem)
{
struct modem_task *task = &modem->task;
modem_function func;
if (!modem->pending_task) {
_DBG_MODEMMGR("%s has no active task", modem->dbus_path);
return;
}
func = task->work->functions[task->index];
if (func == 0 || modem->task.remove_on_complete) {
modem_task_completed(&modem);
return;
}
task->index++;
func(modem);
}
/**
* Rerun the current function in the task
*/
static void modem_task_rerun(struct modem_data *modem)
{
struct modem_task *task = &modem->task;
if (!modem->pending_task) {
_DBG_MODEMMGR("%s has no active task", modem->dbus_path);
return;
}
CONNMAN_ASSERT(task->index > 0);
--task->index;
modem_task_run(modem);
}
/**
* Start running a new list of functions
*/
static int modem_task_run_new(struct modem_data *modem,
struct modem_task_work *work)
{
struct modem_task *task = &modem->task;
if (modem->pending_task) {
_DBG_MODEMMGR("already running %s. Rejecting %s",
task->work->name, work->name);
return -ENXIO; /* crosbug.com/8308 : this should be EAGAIN */
}
modem->pending_task = TRUE;
task->work = work;
task->index = 0;
_DBG_MODEMMGR("%s: %s", modem->dbus_path, task->work->name);
modem_task_run(modem);
return -EINPROGRESS;
}
static void modem_create_device_task(struct modem_data *);
static void modem_disable_task(struct modem_data *);
static void modem_disconnect_task(struct modem_data *);
static void modem_enable_task(struct modem_data *);
static void modem_get_gsm_network_properties_task(struct modem_data *);
static void modem_get_gsm_card_properties_task(struct modem_data *);
static void modem_get_identifiers_task(struct modem_data *);
static void modem_get_info_task(struct modem_data *);
static void modem_get_properties_task(struct modem_data *);
static void modem_get_registration_state_task(struct modem_data *);
static void modem_get_status_task(struct modem_data *);
static void modem_register_task(struct modem_data *);
static void modem_report_enabled_task(struct modem_data *);
static struct modem_task_work initialize_modem_work = {
"initialize", {
modem_get_properties_task,
modem_get_gsm_card_properties_task,
modem_create_device_task,
0}};
static struct modem_task_work enable_cdma_modem_work = {
"enable_cdma", {
modem_enable_task,
modem_get_status_task,
modem_get_identifiers_task,
modem_get_info_task,
modem_get_registration_state_task,
modem_report_enabled_task,
0}};
static struct modem_task_work enable_gsm_modem_work = {
"enable_gsm", {
modem_enable_task,
modem_register_task,
modem_get_status_task,
modem_get_identifiers_task,
modem_get_gsm_network_properties_task,
modem_get_info_task,
modem_get_registration_state_task,
modem_report_enabled_task,
0}};
/* If you ever adjust the contents of this list, please see the block comment in
* modem_device_disable().
*/
static struct modem_task_work disable_connected_modem_work = {
"disable_connected", {
modem_disconnect_task,
modem_disable_task,
0}};
static struct modem_task_work disable_modem_work = {
"disable", {
modem_disable_task,
0}};
/* If you ever adjust the contents of this list, please see the block comment in
* modem_device_disable().
*/
static struct modem_task_work disconnect_modem_work = {
"disconnect", {
modem_disconnect_task,
0}};
static DBusConnection *connection;
static const char *modem_managers;
static enum connman_element_error convert_error(const DBusError *error)
{
if (!dbus_error_is_set(error))
return CONNMAN_ELEMENT_ERROR_NO_ERROR;
if (dbus_error_has_name(error, kErrorIncorrectPassword))
return CONNMAN_ELEMENT_ERROR_INCORRECT_PIN;
else if (dbus_error_has_name(error, kErrorSimPinRequired))
return CONNMAN_ELEMENT_ERROR_PIN_REQUIRED;
else if (dbus_error_has_name(error, kErrorSimPukRequired))
return CONNMAN_ELEMENT_ERROR_PIN_BLOCKED;
else
return CONNMAN_ELEMENT_ERROR_INTERNAL;
}
static enum connman_element_error convert_register_error(const DBusError *error)
{
if (!dbus_error_is_set(error))
return CONNMAN_ELEMENT_ERROR_NO_ERROR;
// TODO(ers) handle ModemManager errors
return CONNMAN_ELEMENT_ERROR_INTERNAL;
}
/* ========================== */
/* Modem device object for 3G */
/* ========================== */
static struct modem_data *modem_device_find_modem(struct connman_device *device,
const char *func)
{
const char *path = connman_device_get_string(device, "DBus.Object");
if (path == NULL) {
_DBG_MODEMMGR("%s: Cannot find modem for %p", func, device);
return NULL;
}
return g_hash_table_lookup(modem_hash, path);
}
static int modem_device_probe(struct connman_device *device)
{
_DBG_MODEMMGR("device %p", device);
return 0;
}
static void modem_device_remove(struct connman_device *device)
{
_DBG_MODEMMGR("device %p", device);
}
/**
* Connman has requested we enable the modem.
*
*/
static int modem_device_enable(struct connman_device *device)
{
struct modem_data *modem;
modem = modem_device_find_modem(device, __func__);
if (modem == NULL) {
return -ENODEV;
}
switch (modem->state) {
case MODEM_STATE_DISABLED:
switch (modem->type) {
case MM_MODEM_TYPE_CDMA:
return modem_task_run_new(modem,
&enable_cdma_modem_work);
case MM_MODEM_TYPE_GSM:
return modem_task_run_new(modem,
&enable_gsm_modem_work);
}
return -EIO;
case MODEM_STATE_ENABLED:
case MODEM_STATE_REGISTERED:
case MODEM_STATE_CONNECTED:
return 0;
default:
_DBG_MODEMMGR("Invalid state %s\n", stateToString(modem->state));
return -EIO;
}
}
/*
* modem_device_disable
*
* Handle a request from connman to disable a modem (turn off the radio).
*/
static int modem_device_disable(struct connman_device *device)
{
struct modem_data *modem;
modem = modem_device_find_modem(device, __func__);
_DBG_MODEMMGR("modem %p", modem);
if (modem == NULL) {
return -ENODEV;
}
switch (modem->state) {
case MODEM_STATE_DISABLED:
case MODEM_STATE_FAILED:
case MODEM_STATE_REMOVED:
/* It is already off, we are done */
return 0;
case MODEM_STATE_ENABLED:
case MODEM_STATE_REGISTERED:
return modem_task_run_new(modem,
&disable_modem_work);
case MODEM_STATE_CONNECTED:
/* Fixes crosbug.com/9371.
* That bug occurs when connman is asked (by the UI) to disable
* a connected device; connman's disable code first destroys all
* the device's networks (kicking off disconnects), then
* immediately tells us to disable the device. We'll notice that
* we're mid-disconnect and fail to start the disable_connected
* task, so instead we just switch over to the disable_connected
* task from the disconnect task.
*
* Important safety tip: If the disable_connected and disconnect
* tasks ever get out of sync, this will cause subtle
* misbehaviors under certain race conditions, like certain
* steps getting executed twice or not at all. If you change one
* of those lists, remember that you may need to tweak the index
* here.
*/
if (modem->task.work == &disconnect_modem_work) {
_DBG_MODEMMGR("switching states...");
/* The task index is always incremented before running
* the next task, so if it's <= 0, we're in real
* trouble...
*/
CONNMAN_ASSERT(modem->task.index > 0);
CONNMAN_ASSERT(disconnect_modem_work.functions[modem->task.index - 1] ==
disable_connected_modem_work.functions[modem->task.index - 1]);
modem->task.work = &disable_connected_modem_work;
return 0;
}
return modem_task_run_new(modem,
&disable_connected_modem_work);
default:
_DBG_MODEMMGR("Invalid state %s\n", stateToString(modem->state));
return -EIO;
}
}
static int modem_device_require_pin(struct connman_device *device,
const char *pin, connman_bool_t require,
struct connman_dbus_method_callback *callback);
static int modem_device_enter_pin(struct connman_device *device,
const char *pin,
struct connman_dbus_method_callback *callback);
static int modem_device_unblock_pin(struct connman_device *device,
const char *unblock_code, const char *pin,
struct connman_dbus_method_callback *callback);
static int modem_device_change_pin(struct connman_device *device,
const char *old_pin, const char *new_pin,
struct connman_dbus_method_callback *callback);
static int modem_device_scan(struct connman_device *device);
static int modem_device_register(struct connman_device *device,
const char *network_id,
struct connman_dbus_method_callback *callback);
static struct connman_device_driver modem_driver = {
.name = "modem",
.type = CONNMAN_DEVICE_TYPE_CELLULAR,
.probe = modem_device_probe,
.remove = modem_device_remove,
.enable = modem_device_enable,
.disable = modem_device_disable,
.scan = modem_device_scan,
.register_on_network
= modem_device_register,
.require_pin = modem_device_require_pin,
.enter_pin = modem_device_enter_pin,
.unblock_pin = modem_device_unblock_pin,
.change_pin = modem_device_change_pin
};
/* modem_dbus_build_message:
* @destination: DBus destination char*
* @interface: DBus interface char*
* @path: DBus path char*
* @message: pointer to DBusMessage*
* @method: method to call
*
* Copy the path to path_copy and build up a call to the specified
* modem method.
*/
static int modem_dbus_build_message(const char *destination,
const char *interface,
const char *path,
DBusMessage **message,
const char *method)
{
*message = dbus_message_new_method_call(destination,
path,
interface,
method);
if (message == NULL) {
return -ENOMEM;
}
dbus_message_set_auto_start(*message, FALSE);
return 0;
}
/* The D-Bus API requires that the following variable be global. */
static dbus_int32_t modem_pending_call_slot = -1;
static DBusMessage *modem_dbus_steal_reply(DBusPendingCall *call)
{
struct modem_data *modem;
modem = dbus_pending_call_get_data(call, modem_pending_call_slot);
CONNMAN_ASSERT(modem != NULL);
CONNMAN_ASSERT(g_slist_find(modem->pending_dbus_calls, call));
DBusMessage *reply = dbus_pending_call_steal_reply(call);
dbus_pending_call_unref(call);
modem->pending_dbus_calls =
g_slist_remove(modem->pending_dbus_calls, call);
return reply;
}
static void modem_dbus_cancel_pending_calls(struct modem_data *modem)
{
GSList *item = modem->pending_dbus_calls;
while (item != NULL) {
_DBG_MODEMMGR("Cancelling pending call");
dbus_pending_call_cancel(item->data);
dbus_pending_call_unref(item->data);
item = g_slist_next(item);
}
g_slist_free(modem->pending_dbus_calls);
modem->pending_dbus_calls = NULL;
}
/**
* modem_dbus_send_with_reply
* @modem: the modem object
* @message: pointer to DBusMessage*
* @user_data: data for callback function
* @function: completion callback
* @timeout_ms: DBus timeout in milliseconds
*/
static int modem_dbus_send_with_reply(struct modem_data *modem,
DBusMessage *message,
void *user_data,
DBusPendingCallNotifyFunction function,
int timeout_ms)
{
int success = FALSE;
DBusPendingCall *call;
_DBG_MODEMMGR("Sending %s.%s timeout: %d",
dbus_message_get_interface(message),
dbus_message_get_member(message),
timeout_ms);
if (dbus_connection_send_with_reply(connection, message,
&call, timeout_ms) == FALSE) {
CONNMAN_ERROR("Failed to get interface");
goto error;
}
if (call == NULL) {
CONNMAN_ERROR("D-Bus connection not available");
goto error;
}
dbus_pending_call_set_notify(call, function, user_data, NULL);
dbus_pending_call_set_data(call, modem_pending_call_slot, modem, NULL);
/*
* Remember the pending call. We hold a reference, in
* addition to the reference held by the DBus library.
*/
modem->pending_dbus_calls = g_slist_prepend(modem->pending_dbus_calls,
call);
success = TRUE;
error:
dbus_message_unref(message);
if (success == FALSE) {
return -EIO;
}
return 0;
}
/**
* modem_dbus_send_message_valist:
* @interface: DBus interface char*
* @method: method to call
* @func: function to call on reply.
* @timeout_ms: DBus timeout in milliseconds
* @user_data: an object to be passed to the notify function
* @event: event to handle on errror
* @first_arg_type, ... message arguments
*
* Send a DBUS message. The message arguments can be specified with
* with <type>, <variable address> pairs, terminated by DBUS_TYPE_INVALID.
* If any errors occur, signal the internal modem event.
*/
static int modem_dbus_send_message_valist(struct modem_data *modem,
const char *interface,
const char *method,
DBusPendingCallNotifyFunction func,
void *user_data,
int timeout_ms,
enum modem_event event,
int first_arg_type,
va_list args)
{
DBusMessage *message;
int err;
err = modem_dbus_build_message(modem->owner, interface,
modem->dbus_path, &message, method);
if (err < 0) {
if (event != ME_NO_EVENT)
modem_handle_event(modem, event);
return err;
}
if (first_arg_type != DBUS_TYPE_INVALID) {
dbus_message_append_args_valist(message, first_arg_type, args);
}
err = modem_dbus_send_with_reply(modem, message,
user_data, func, timeout_ms);
if (err < 0 && event != ME_NO_EVENT)
modem_handle_event(modem, event);
return err;
}
/* Non-va_list version of modem_dbus_send_message_valist */
static int modem_dbus_send_message(struct modem_data *modem,
const char *interface,
const char *method,
DBusPendingCallNotifyFunction func,
enum modem_event event,
int first_arg_type, ...
)
{
int err;
va_list var_args;
va_start(var_args, first_arg_type);
err = modem_dbus_send_message_valist(modem, interface, method, func,
modem, DEFAULT_TIMEOUT_MS,
event, first_arg_type, var_args);
va_end(var_args);
return err;
}
/* Non-va_list version of modem_dbus_send_message_valist */
static int modem_dbus_send_message_with_timeout(struct modem_data *modem,
const char *interface,
const char *method,
DBusPendingCallNotifyFunction func,
int timeout_ms,
enum modem_event event,
int first_arg_type, ...
)
{
int err;
va_list var_args;
va_start(var_args, first_arg_type);
err = modem_dbus_send_message_valist(modem, interface, method, func,
modem, timeout_ms,
event, first_arg_type, var_args);
va_end(var_args);
return err;
}
/**
* modem_dbus_handle_reply
* @func - name of the function that called this, for debug messages
* @call - the dbus call, contains the reply message, etc
* @modem - the modem object
* @success_event - move the state machine based on this event if
* there are no errors.
* @failure_event - move the state machine based on this event if
* there are errors.
* first_arg_type, ... arguments to extract from the reply. These
* typically should point to fields of the modem.
*
* Handle a dbus reply message by checking for DBUS errors, extracting
* arguments, and then moving the state machine based on either the
* success event or the failure event.
*/
static void modem_dbus_handle_reply(const char *func,
DBusPendingCall *call,
struct modem_data *modem,
enum modem_event success_event,
enum modem_event failure_event,
int first_arg_type, ...
)
{
DBusMessage *reply;
DBusError error;
enum modem_event event = success_event;
va_list var_args;
reply = modem_dbus_steal_reply(call);
if (reply == NULL) {
CONNMAN_ERROR("%s: Failed steal reply", func);
modem_handle_event(modem, failure_event);
return;
}
dbus_error_init(&error);
va_start(var_args, first_arg_type);
if (dbus_set_error_from_message(&error, reply)) {
/*
* OperationInitiated errors are always ignored
* along with the reply itself, with the expectation
* that a completion event of some kind will arrive
* later.
*/
if (dbus_error_has_name(&error, kErrorOperationInitiated)) {
event = ME_NO_EVENT;
} else {
_DBG_MODEMMGR("%s: Unexpected error %s: %s",
func, error.name, error.message);
event = failure_event;
}
} else if ((dbus_message_get_args_valist(reply, &error,
first_arg_type, var_args)) == FALSE) {
if (dbus_error_is_set(&error) == TRUE) {
_DBG_MODEMMGR("%s: Error %s: %s", func, error.name,
error.message);
event = failure_event;
}
}
va_end(var_args);
dbus_error_free(&error);
dbus_message_unref(reply);
_DBG_MODEMMGR("%s: Event %s", func, eventToString(event));
if (event != ME_NO_EVENT)
modem_handle_event(modem, event);
}
/**
* modem_dbus_handle_reply_with_pending
* @call - the dbus call, contains the reply message, etc
* @user_data - a pointer to the modem_data structure
*
* Handle a dbus reply message by checking for D-Bus errors,
* and sending either an error msesage or a method reply message
* back to the original caller who invoked the flimflam method.
*/
static void modem_dbus_handle_reply_with_pending(DBusPendingCall *call,
void *user_data)
{
struct modem_method_callback *cb = user_data;
DBusMessage *reply;
DBusError dberror;
enum connman_element_error error;
_DBG_MODEMMGR("Reply for async operation");
reply = modem_dbus_steal_reply(call);
if (reply == NULL) {
CONNMAN_ERROR("%s: Failed steal reply", __func__);
connman_dbus_invoke_callback(
cb->cb_data,
CONNMAN_ELEMENT_ERROR_INTERNAL);
g_free(cb);
return;
}
dbus_error_init(&dberror);
if (dbus_set_error_from_message(&dberror, reply)) {
error = cb->convert_error(&dberror);
} else {
error = CONNMAN_ELEMENT_ERROR_NO_ERROR;
}
dbus_error_free(&dberror);
dbus_message_unref(reply);
connman_dbus_invoke_callback(cb->cb_data, error);
g_free(cb);
}
/**
* modem_dbus_send_message_with_pending:
* @modem: the modem object
* @callback: object to be passed to the reply handler function
* @timeout_ms: DBus timeout in milliseconds
* @interface: DBus interface name
* @method: method to call
* @first_arg_type, ... message arguments
*
* Send a DBUS message. The message arguments can be specified with
* with <type>, <variable address> pairs, terminated by DBUS_TYPE_INVALID.
* This differs from modem_dbus_send_message in that it is used when an
* async flimflam method has been invoked, and a reply message must be
* sent back to the caller on completion.
*/
static int modem_dbus_send_message_with_pending(struct modem_data *modem,
void *callback,
int timeout_ms,
const char *interface,
const char *method,
int first_arg_type, ...)
{
int err = 0;
_DBG_MODEMMGR("modem %s method %s", modem->dbus_path, method);
va_list var_args;
va_start(var_args, first_arg_type);
err = modem_dbus_send_message_valist(
modem,
interface,
method,
modem_dbus_handle_reply_with_pending,
callback,
timeout_ms,
ME_NO_EVENT,
first_arg_type, var_args);
va_end(var_args);
return err;
}
#define VALID_DBUS_CHAR(c) \
(('A' <= (c) && (c) <= 'Z') || \
('a' <= (c) && (c) <= 'z') || \
('0' <= (c) && (c) <= '9') || \
((c) == '_'))
/*
* Convert all characters that aren't valid in D-Bus
* names to underscore characters. Returned name must
* be freed by the caller.
*/
static char *sanitize_dbus_name(const char *name)
{
char *sani_name = g_strdup(name);
char *cp;
for (cp = sani_name; *cp; cp++) {
if (!VALID_DBUS_CHAR(*cp))
*cp = '_';
}
return sani_name;
}
static struct connman_network *get_network(struct modem_data *modem)
{
if (modem->network_dbus_name == NULL)
return NULL;
return connman_device_get_network(modem->cm_device,
modem->network_dbus_name);
}
/* Create a network element */
static struct connman_network *create_network(struct modem_data *modem)
{
static unsigned int netid = 0;
char namebuf[sizeof(NETWORK_NAME) + 16];
struct connman_network *network;
struct connman_network_operator *home;
int rc;
int index;
const char *net_display_name = NULL;
_DBG_MODEMMGR("path %s", modem->dbus_path);
home = connman_device_get_home_provider(modem->cm_device);
if (home != NULL &&
modem->type == MM_MODEM_TYPE_GSM &&
(modem->gsm.registration_state ==
MM_MODEM_GSM_NETWORK_REG_STATUS_HOME))
net_display_name = home->name;
if (net_display_name == NULL) {
if (modem->gsm.operator_name != NULL &&
strlen(modem->gsm.operator_name) != 0)
net_display_name = modem->gsm.operator_name;
else if (modem->configured_carrier != NULL)
net_display_name = modem->configured_carrier;
else if (modem->gsm.network_id != NULL) {
snprintf(namebuf, sizeof(namebuf), "cellular_%s",
modem->gsm.network_id);
net_display_name = namebuf;
} else {
snprintf(namebuf, sizeof(namebuf), "%s%d", NETWORK_NAME, netid);
netid++;
net_display_name = namebuf;
}
}
if (modem->imsi != NULL && modem->type == MM_MODEM_TYPE_GSM)
modem->network_dbus_name = sanitize_dbus_name(modem->imsi);
else
modem->network_dbus_name = sanitize_dbus_name(net_display_name);
network = connman_device_get_network(modem->cm_device,
modem->network_dbus_name);
if (network != NULL) {
_DBG_MODEMMGR("return existing network object %p", network);
return network; /* XXX(ers) error? */
}
/*
* Assume a single network is available for a 3G modem. This
* may change for multi-mode cards.
*/
network = connman_network_create(modem->network_dbus_name,
CONNMAN_NETWORK_TYPE_CELLULAR);
if (network == NULL) {
return NULL;
}
rc = connman_network_set_string(network, "DBus.Object",
modem->dbus_path);
if (rc < 0) {
connman_network_unref(network);
return NULL;
}
rc = connman_network_set_available(network, TRUE);
if (rc < 0) {
connman_network_unref(network);
return NULL;
}
index = connman_device_get_index(modem->cm_device);
connman_network_set_index(network, index);
rc = connman_device_add_network(modem->cm_device, network);
if (rc < 0) {
connman_network_unref(network);
return NULL;
}
connman_network_set_protocol(network, CONNMAN_NETWORK_PROTOCOL_IP);
connman_network_set_name(network, net_display_name);
connman_network_set_scangen(network, ++modem->scangen);
connman_network_set_group(network, modem->network_dbus_name);
connman_network_set_strength(network, modem->signal_strength);
connman_network_set_registration_info(network,
modem->network_technology,
CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN);
if (modem->type == MM_MODEM_TYPE_CDMA) {
connman_network_set_activation_state(network,
modem->cdma.activation_state,
CONNMAN_ELEMENT_ERROR_NO_ERROR);
if (modem->configured_carrier != NULL) {
struct connman_network_operator op = {
.name = modem->configured_carrier,
.code = "",
.country = "us"};
connman_network_set_operator(network, &op);
}
} else {
connman_network_set_activation_state(network,
CONNMAN_NETWORK_ACTIVATION_STATE_ACTIVATED,
CONNMAN_ELEMENT_ERROR_NO_ERROR);
}
if (modem->gsm.operator_name != NULL || modem->gsm.network_id != NULL
|| modem->gsm.operator_country != NULL) {
struct connman_network_operator op = {
.name = modem->gsm.operator_name,
.code = modem->gsm.network_id,
.country = modem->gsm.operator_country};
connman_network_set_operator(network, &op);
}
if (modem->olp.url)
connman_network_set_olp_url(network, modem->olp.url,
modem->olp.method, modem->olp.postdata);
if (modem->usage_url)
connman_network_set_usage_url(network, modem->usage_url);
return network;
}
/**
* modem_connect_reply
*
* Handle the reply message from a DBUS_CONNECT call
*/
static void modem_connect_reply(DBusPendingCall *call, void *user_data)
{
struct modem_data *modem = user_data;
DBusMessage *reply;
DBusError error;
dbus_error_init(&error);
reply = modem_dbus_steal_reply(call);
if (reply == NULL) {
modem_connect_failed(modem, MM_NO_RETRY);
return;
}
if (dbus_set_error_from_message(&error, reply)) {
/*
* Some errors are optionally treated as acceptable,
* and handling of the reply is allowed to proceed.
* In addition OperationInitiated errors are always
* ignored along with the reply itself, with the
* expectation that a completion event of some kind
* will arrive later.
*/
_DBG_MODEMMGR("Connect returned error %s: %s",
error.name, error.message);
if (dbus_error_has_name(&error, kErrorOperationInitiated))
goto done;
else if (dbus_error_has_name(&error, kErrorGprsNotSubscribed))
modem_connect_failed(modem, MM_RETRY);
else if (!dbus_error_has_name(&error, kErrorConnected))
modem_connect_failed(modem, MM_NO_RETRY);
else
modem_set_connected(modem, TRUE);
} else {
modem_set_connected(modem, TRUE);
}
done:
dbus_error_free(&error);
dbus_message_unref(reply);
/* If we're roaming, we may not be allowed to have a data connection */
if (modem->gsm.registration_state == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING &&
!connman_device_roaming_allowed(modem->cm_device)) {
connman_error("%s: disconnecting: "
"roaming connections not allowed",
__func__);
modem_task_run_new(modem, &disconnect_modem_work);
}
}
/**
* modem_connect_start:
* @modem: modem object
*
* Issue a DBUS message to connect the modem.
*
* For GSM modems, the APN is looked up in the mobile providers
* database, but if the user has explicitly set the APN for
* a network ID belonging the operator of the currently
* registered network, the user-set APN is used instead of
* the one from the database.
*/
static int modem_connect_start(struct modem_data *modem)
{
DBusMessageIter dict, array;
DBusMessage *message;
connman_bool_t home_only;
int rc = 0;
struct connman_network *network = get_network(modem);
connman_network_set_connecting(network);
rc = modem_dbus_build_message(modem->owner, MM_MODEM_SIMPLE_INTERFACE,
modem->dbus_path, &message,
MM_MODEM_SIMPLE_METHOD_CONNECT);
if (rc < 0) {
modem_connect_failed(modem, MM_NO_RETRY);
return rc;
}
dbus_message_iter_init_append(message, &array);
dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
connman_dbus_dict_append_variant(&dict, "number",
DBUS_TYPE_STRING,
&modem->phone_number);
if (modem->gsm.next_apn.apn != NULL) {
connman_dbus_dict_append_variant(&dict, "apn",
DBUS_TYPE_STRING,
&modem->gsm.next_apn.apn);
if (modem->gsm.next_apn.username != NULL) {
connman_dbus_dict_append_variant(&dict, "username",
DBUS_TYPE_STRING,
&modem->gsm.next_apn.username);
}
if (modem->gsm.next_apn.password != NULL) {
connman_dbus_dict_append_variant(&dict, "password",
DBUS_TYPE_STRING,
&modem->gsm.next_apn.password);
}
}
home_only = !connman_device_roaming_allowed(modem->cm_device);
if (home_only)
connman_dbus_dict_append_variant(&dict, "home_only",
DBUS_TYPE_BOOLEAN,
&home_only);
++modem->gsm.connect_tries;
dbus_message_iter_close_container(&array, &dict);
rc = modem_dbus_send_with_reply(modem, message,
modem, modem_connect_reply,
CONNECT_TIMEOUT_MS);
if (rc < 0) {
modem_connect_failed(modem, MM_NO_RETRY);
return rc;
}
return -EINPROGRESS;
}
/**
* For GSM modems, remember the APN that was used to successfully
* connect, so that the same APN can be used next time without
* having to try other APNs that we already know don't work.
*/
static void modem_record_connect_info(struct modem_data *modem)
{
struct connman_network *network;
if (modem->type != MM_MODEM_TYPE_GSM ||
modem->state != MODEM_STATE_CONNECTED)
return;
if (modem->gsm.next_apn.apn != NULL &&
modem->gsm.next_apn.apn[0] != '\0') {
_DBG_MODEMMGR("Last successful APN: \"%s\" \"%s\" \"%s\"",
modem->gsm.next_apn.apn,
modem->gsm.next_apn.username,
modem->gsm.next_apn.password);
network = get_network(modem);
connman_network_save_last_good_apn(network, &modem->gsm.next_apn);
} else {
/* If the modem was already started when we started
up, then we might not have fetched a valid APN yet */
_DBG_MODEMMGR("APN was null");
}
}
static void modem_set_powered_failed(struct modem_data *modem,
enum connman_element_error error);
/**
* modem_enable_reply:
* @modem: modem object
* @user_data: pointer to modem_task object
*
* Handle the reply from a DBUS enable(TRUE) message.
*/
static void modem_enable_reply(DBusPendingCall *call, void *user_data)
{
struct modem_data *modem = user_data;
DBusMessage *reply;
DBusError error;
enum modem_event event = ME_ENABLE_SUCCEEDED;
_DBG_MODEMMGR("");
reply = modem_dbus_steal_reply(call);
if (reply == NULL) {
CONNMAN_ERROR("Failed steal reply");
modem_set_powered_failed(modem, convert_error(&error));
modem_handle_event(modem, ME_ENABLE_FAILED);
return;
}
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, reply)) {
/*
* OperationInitiated errors are always ignored
* along with the reply itself, with the expectation
* that a completion event of some kind will arrive
* later.
*/
if (!dbus_error_has_name(&error, kErrorOperationInitiated)) {
_DBG_MODEMMGR("Enable failed %s: %s",
error.name, error.message);
modem_set_powered_failed(modem, convert_error(&error));
event = ME_ENABLE_FAILED;
}
}
dbus_error_free(&error);
dbus_message_unref(reply);
modem_handle_event(modem, event);
}
/**
* modem_disable_reply:
* @modem: modem object
* @user_data: pointer to modem_task object
*
* Handle the reply from a DBUS enable(FALSE) message.
*/
static void modem_disable_reply(DBusPendingCall *call, void *user_data)
{
struct modem_data *modem = user_data;
modem_dbus_handle_reply(__func__, call, modem,
ME_DISABLE_SUCCEEDED, ME_DISABLE_FAILED,
DBUS_TYPE_INVALID /* no return values */);
}
/**
* modem_enable_task:
* @modem: modem object
* @task: modem task object
*
* Send a DBUS message to enable a modem. As a task, all failures
* must be reported via modem_handle_event.
*/
static void modem_enable_task(struct modem_data *modem)
{
int enable = TRUE;
_DBG_MODEMMGR("enable modem %s", modem->dbus_path);
modem_dbus_send_message(modem,
MM_MODEM_INTERFACE, MM_MODEM_METHOD_ENABLE,
modem_enable_reply, ME_ENABLE_FAILED,
DBUS_TYPE_BOOLEAN, &enable,
DBUS_TYPE_INVALID);
}
/**
* modem_disable_task:
* @modem: modem object
* @task: modem task object
*
* Send a DBUS message to disable a modem. As a task, all failures
* must be reported via modem_handle_event.
*/
static void modem_disable_task(struct modem_data *modem)
{
int enable = FALSE;
_DBG_MODEMMGR("disable modem %s", modem->dbus_path);
modem_dbus_send_message(modem,
MM_MODEM_INTERFACE, MM_MODEM_METHOD_ENABLE,
modem_disable_reply, ME_DISABLE_FAILED,
DBUS_TYPE_BOOLEAN, &enable,
DBUS_TYPE_INVALID);
}
static void modem_set_powered(struct modem_data *modem, gboolean powered);
static int modem_registration_state_change(struct modem_data *modem);
/**
* modem_report_enabled_task
* @modem: modem object
*
* Report to flimflam core that the modem is enabled.
*/
static void modem_report_enabled_task(struct modem_data *modem) {
_DBG_MODEMMGR("");
modem_set_powered(modem, TRUE);
/* In case we deferred a registration state change while enabling */
modem_registration_state_change(modem);
modem_task_run(modem);
}
/**
* Handle a change in activation state
*/
static void modem_update_activation_state(struct modem_data *modem,
enum connman_network_activation_state state,
enum connman_element_error err)
{
struct connman_network *network = get_network(modem);
if (network != NULL) {
modem->cdma.activation_state = state;
connman_network_set_activation_state(network, state, err);
}
}
/**
* modem_activate_reply:
* @modem: modem object
* @user_data: pointer to modem_task object
*
* Handle the reply from a DBUS activate message. Certain errors are
* indicative of the modem already being activated, so if that is the
* case, we can proceed despite the error.
*/
static void modem_activate_reply(DBusPendingCall *call, void *user_data)
{
struct modem_data *modem = user_data;
uint32_t status = -1;
DBusMessage *reply;
DBusError error;
dbus_error_init(&error);
reply = modem_dbus_steal_reply(call);
if (reply == NULL) {
CONNMAN_ERROR("%s: Failed steal reply", __func__);
goto done_no_reply;
}
if (dbus_set_error_from_message(&error, reply)) {
if (dbus_error_has_name(&error, kErrorOperationInitiated)) {
/*
* Activation can take a long while. We rely
* on a signal to indicate a future state
* change of the modem, or for the modem to
* disappear. Nothing to do.
*/
dbus_message_unref(reply);
return;
}
CONNMAN_ERROR("DBus Error: %s:%s",
error.name, error.message);
goto done;
}
if (dbus_message_get_args(reply, &error,
DBUS_TYPE_UINT32,
&status,
DBUS_TYPE_INVALID) == FALSE) {
if (dbus_error_is_set(&error) == TRUE) {
CONNMAN_ERROR("Error retrieving reply argument: %s:%s",
error.name, error.message);
} else {
CONNMAN_ERROR("Reply argument type is not UINT32");
}
}
done:
dbus_message_unref(reply);
done_no_reply:
if (status != 0) {
modem_update_activation_state(modem,
/* FIXME(jglasgow): need to leave state unchanged */
CONNMAN_NETWORK_ACTIVATION_STATE_NOT_ACTIVATED,
convert_activation_error(status));
}
/*
* For the case of status == 0, we rely either on an explicit
* signal of the new modem state, or we expect the modem to
* disappear, reset itself and come back
*/
}
/**
* modem_register_reply:
* @modem: modem object
* @user_data: pointer to modem_task object
*
* Handle the reply from a DBUS register message.
*/
static void modem_register_reply(DBusPendingCall *call, void *user_data)
{
struct modem_data *modem = user_data;
modem_dbus_handle_reply(__func__, call, modem,
ME_REGISTER_SUCCEEDED, ME_REGISTER_FAILED,
DBUS_TYPE_INVALID /* no return values */);
}
/**
* modem_register_task:
* @modem: modem object
* @task: modem task object
*
* Send a DBUS message to register a GSM modem. As a task, all failures
* must be reported via modem_handle_event.
*/
static void modem_register_task(struct modem_data *modem)
{
const char *network_id;
network_id = connman_device_get_selected_network(modem->cm_device);
_DBG_MODEMMGR("register modem %s on %s", modem->dbus_path,
network_id ? network_id : "home network");
if (network_id == NULL)
network_id = ""; /* Use home network */
modem_dbus_send_message_with_timeout(modem,
MM_MODEM_GSM_NETWORK_INTERFACE,
MM_MODEM_GSM_NETWORK_METHOD_REGISTER,
modem_register_reply,
NETWORK_REGISTER_TIMEOUT_MS,
ME_REGISTER_FAILED,
DBUS_TYPE_STRING, &network_id,
DBUS_TYPE_INVALID);
}
static void free_dict(gpointer data)
{
g_hash_table_destroy(data);
}
static GPtrArray *make_apn_list(struct mobile_provider *provider)
{
int i, j;
GPtrArray *apn_list;
if (provider == NULL)
return NULL;
apn_list = g_ptr_array_sized_new(provider->num_apns);
g_ptr_array_set_free_func(apn_list, free_dict);
for (i = 0; i < provider->num_apns; ++i) {
struct mobile_apn *apn = provider->apns[i];
GHashTable *props = g_hash_table_new_full(g_str_hash,
g_str_equal,
NULL, g_free);
struct localized_name *name = NULL;
struct localized_name *lname = NULL;
if (apn->value != NULL)
g_hash_table_insert(props, "apn", g_strdup(apn->value));
if (apn->username != NULL)
g_hash_table_insert(props, "username",
g_strdup(apn->username));
if (apn->password != NULL)
g_hash_table_insert(props, "password",
g_strdup(apn->password));
/*
* Find the first localized name, if any, and
* the first non-localized name, if any.
*/
for (j = 0; j < apn->num_names; ++j) {
if (apn->names[j]->lang != NULL) {
if (lname == NULL)
lname = apn->names[j];
} else if (name == NULL) {
name = apn->names[j];
}
}
if (name != NULL)
g_hash_table_insert(props, "name", g_strdup(name->name));
if (lname != NULL) {
g_hash_table_insert(props, "localized_name",
g_strdup(lname->name));
g_hash_table_insert(props, "language",
g_strdup(lname->lang));
}
g_ptr_array_add(apn_list, props);
}
return apn_list;
}
/**
* set_gsm_provider:
*
* Utility routine to set the upper level information about the
* cellular provider from the modem's IMSI.
*/
static void set_gsm_provider(struct modem_data *modem,
struct connman_device *device)
{
struct mobile_provider *provider = NULL;
GPtrArray *apn_list;
_DBG_MODEMMGR("");
if (modem->imsi == NULL)
return;
_DBG_MODEMMGR("imsi '%s' spn '%s'", modem->imsi, modem->gsm.spn);
provider = mobile_provider_lookup_best_match(provider_db,
modem->gsm.spn,
modem->imsi);
if (provider != NULL) {
modem->gsm.provider = provider;
struct connman_network_operator op = {
.code = provider->networks ? provider->networks[0] : NULL,
.country = provider->country};
if (modem->gsm.spn != NULL && strlen(modem->gsm.spn) != 0)
op.name = g_strdup(modem->gsm.spn);
else
op.name = g_strdup(mobile_provider_get_name(provider));
connman_device_set_home_provider(device, &op);
g_free(op.name);
apn_list = make_apn_list(provider);
if (apn_list != NULL)
connman_device_set_apn_list(device, apn_list);
connman_device_set_provider_requires_roaming(device,
provider->requires_roaming);
}
}
/**
* modem_create_device_task - create a connman device object for a modem
*
*/
static void modem_create_device_task(struct modem_data *modem)
{
struct connman_device *device;
int index;
_DBG_MODEMMGR("path %s interface %s", modem->dbus_path,
modem->device);
if (modem->ip_method != MM_MODEM_IP_METHOD_DHCP) {
/* PPP is not supported */
CONNMAN_ERROR("Unsupported modem IP method %d",
modem->ip_method);
modem_handle_event(modem, ME_CREATE_DEVICE_FAILED);
return;
}
index = connman_inet_ifindex(modem->device);
device = connman_inet_create_device(index);
if (device == NULL) {
CONNMAN_ERROR("Cannot create modem device");
modem_handle_event(modem, ME_CREATE_DEVICE_FAILED);
return;
}
connman_device_set_string(device, "DBus.Object", modem->dbus_path);
connman_device_set_string(device, "DBus.Service", modem->service);
connman_device_set_string(device, "DBus.Connection", modem->owner);
connman_device_set_string(device, "Tty", modem->device);
if (modem->configured_carrier != NULL)
connman_device_set_string(device, "Cellular.Carrier",
modem->configured_carrier);
if (modem->meid != NULL)
connman_device_set_string(device, "Cellular.MEID",
modem->meid);
if (modem->imei != NULL)
connman_device_set_string(device, "Cellular.IMEI",
modem->imei);
if (modem->imsi != NULL)
connman_device_set_string(device, "Cellular.IMSI",
modem->imsi);
switch (modem->type) {
case MM_MODEM_TYPE_CDMA:
connman_device_set_cellular_family(device,
CONNMAN_DEVICE_CELLULAR_FAMILY_CDMA);
/*
* TODO(jglasgow): phone number should come
* from configuration information
*/
modem->phone_number = g_strdup("#777");
if (modem->configured_carrier != NULL) {
/*
* Assume US for the country, since CDMA barely
* exists elsewhere (not actually correct, but
* it'll do for now).
*/
struct connman_network_operator op = {
.name = modem->configured_carrier,
.code = "",
.country = "us"};
connman_device_set_home_provider(device, &op);
connman_device_set_string(device,
"Cellular.FirmwareImageName",
modem->configured_carrier);
}
break;
case MM_MODEM_TYPE_GSM:
connman_device_set_cellular_family(device,
CONNMAN_DEVICE_CELLULAR_FAMILY_GSM);
modem->phone_number = g_strdup("*99#");
set_gsm_provider(modem, device);
connman_device_set_unlock_properties(device,
modem->gsm.lock.unlock_required,
modem->gsm.lock.retries,
modem->gsm.lock.enabled);
break;
}
connman_device_set_scanning_supported(device,
modem->type == MM_MODEM_TYPE_GSM);
/* We are now done with the creation, and we leave modem DISABLED */
modem_task_completed(&modem);
/* modem may have been freed by modem_task_completed() */
if (modem == NULL)
return;
modem->state = MODEM_STATE_DISABLED;
if (connman_device_register(device) < 0) {
connman_device_unref(device);
modem_handle_event(modem, ME_CREATE_DEVICE_FAILED);
return;
}
modem->cm_device = device;
}
/**
* modem_disconnect_reply:
* @user_data: pointer to modem_task object
*
* Handle the reply from a DBUS disconnect message. by running the
* state machine.
*/
static void modem_disconnect_reply(DBusPendingCall *call, void *user_data)
{
struct modem_data *modem = user_data;
modem_dbus_handle_reply(__func__, call, modem,
ME_DISCONNECT_SUCCEEDED, ME_DISCONNECT_FAILED,
DBUS_TYPE_INVALID /* no return values */);
}
/**
* modem_disconnect_task
* @modem: modem object
*
* Send a DBUS message to disconnect a modem
*/
static void modem_disconnect_task(struct modem_data *modem)
{
modem_dbus_send_message(modem,
MM_MODEM_INTERFACE,
MM_MODEM_METHOD_DISCONNECT,
modem_disconnect_reply,
ME_DISCONNECT_FAILED,
DBUS_TYPE_INVALID);
}
/**
* modem_get_cdma_registration_state_reply
* @user_data: pointer to modem_task object
*
* Handle the reply from a DBUS get_registration_state message. Run
* the state machine.
*/
static void modem_get_cdma_registration_state_reply(DBusPendingCall *call, void *user_data)
{
struct modem_data *modem = user_data;
modem_dbus_handle_reply(__func__, call, modem,
ME_GET_REGISTRATION_STATE_SUCCEEDED,
ME_GET_REGISTRATION_STATE_FAILED,
DBUS_TYPE_UINT32, &modem->cdma._1x_registration_state,
DBUS_TYPE_UINT32, &modem->cdma.evdo_registration_state,
DBUS_TYPE_INVALID);
if (modem->connect_needed)
handle_reported_connect(modem);
}
static void set_network_id(struct modem_data *modem, const char *network_id)
{
struct connman_network *network = get_network(modem);
if (strlen(network_id) == 0)
network_id = NULL;
g_free(modem->gsm.network_id);
modem->gsm.network_id = g_strdup(network_id);
if (network_id != NULL) {
const gchar *provider_name;
struct mobile_provider *provider;
provider = mobile_provider_lookup_by_network(provider_db,
network_id);
if (provider == NULL)
_DBG_MODEMMGR("GSM provider for network %s not found",
network_id);
else {
provider_name = mobile_provider_get_name(provider);
_DBG_MODEMMGR("Found GSM provider %s for network %s",
provider_name, network_id);
g_free(modem->gsm.operator_name);
modem->gsm.operator_name = g_strdup(provider_name);
g_free(modem->gsm.operator_country);
modem->gsm.operator_country =
g_strdup(provider->country);
}
if (network != NULL) {
struct connman_network_operator op = {
.name = modem->gsm.operator_name,
.code = modem->gsm.network_id,
.country = modem->gsm.operator_country};
connman_network_set_operator(network, &op);
if (modem->gsm.operator_name != NULL &&
strlen(modem->gsm.operator_name) != 0)
connman_network_set_name(network,
modem->gsm.operator_name);
}
}
}
/**
* modem_get_gsm_registration_state_reply
* @user_data: pointer to modem_task object
*
* Handle the reply from a DBUS get_registration_state message. Run
* the state machine.
*/
static void modem_get_gsm_registration_state_reply(DBusPendingCall *call,
void *user_data)
{
struct modem_data *modem = user_data;
int failure_event = ME_GET_REGISTRATION_STATE_FAILED;
gboolean do_connect = FALSE;
DBusMessage *reply;
DBusMessageIter reply_iter, values_iter;
enum modem_event event = ME_GET_REGISTRATION_STATE_SUCCEEDED;
gint type;
const char *network_id = NULL, *operator_name = NULL;
reply = modem_dbus_steal_reply(call);
if (reply == NULL) {
CONNMAN_ERROR("%s: Failed steal reply", __func__);
event = failure_event;
goto done_no_reply;
}
if (dbus_message_iter_init(reply, &reply_iter) == FALSE) {
CONNMAN_ERROR("%s: Could not iterate on reply", __func__);
event = failure_event;
goto done;
}
type = dbus_message_iter_get_arg_type(&reply_iter);
if (type != DBUS_TYPE_STRUCT) {
_DBG_MODEMMGR("Expected struct, got %x", type);
event = failure_event;
goto done;
}
dbus_message_iter_recurse(&reply_iter, &values_iter);
dbus_message_iter_get_basic(&values_iter,
&modem->gsm.registration_state);
dbus_message_iter_next(&values_iter);
dbus_message_iter_get_basic(&values_iter, &network_id);
dbus_message_iter_next(&values_iter);
g_free(modem->gsm.operator_name);
dbus_message_iter_get_basic(&values_iter, &operator_name);
modem->gsm.operator_name = g_strdup(operator_name);
set_network_id(modem, network_id);
dbus_message_iter_next(&values_iter);
do_connect = modem->connect_needed;
done:
dbus_message_unref(reply);
done_no_reply:
_DBG_MODEMMGR("%s: Event %s", __func__, eventToString(event));
modem_handle_event(modem, event);
if (do_connect) {
handle_reported_connect(modem);
}
}
/**
* modem_get_registration_state_task
* @modem: modem object
*
* Send a DBUS message to find the registration state/info of a modem
*/
static void modem_get_registration_state_task(struct modem_data *modem)
{
switch (modem->type) {
case MM_MODEM_TYPE_CDMA:
modem_dbus_send_message(
modem,
MM_MODEM_CDMA_INTERFACE,
MM_MODEM_CDMA_METHOD_GETREGISTRATIONSTATE,
modem_get_cdma_registration_state_reply,
ME_GET_REGISTRATION_STATE_FAILED,
DBUS_TYPE_INVALID);
return;
case MM_MODEM_TYPE_GSM:
modem_dbus_send_message(
modem,
MM_MODEM_GSM_NETWORK_INTERFACE,
MM_MODEM_GSM_NETWORK_METHOD_GETREGISTRATIONINFO,
modem_get_gsm_registration_state_reply,
ME_GET_REGISTRATION_STATE_FAILED,
DBUS_TYPE_INVALID);
return;
default:
_DBG_MODEMMGR("Unknown modem type %d", modem->type);
modem_handle_event(modem, ME_GET_REGISTRATION_STATE_FAILED);
break;
}
}
/**
* modem_get_signal_quality_reply
* @user_data: pointer to modem_task object
*
* Handle the reply from a DBUS message with the signal quality
*/
static void modem_get_signal_quality_reply(
DBusPendingCall *call,
void *user_data)
{
struct modem_data *modem = user_data;
modem_dbus_handle_reply(__func__, call, modem,
ME_GET_SIGNALQUALITY_SUCCEEDED,
ME_GET_SIGNALQUALITY_FAILED,
DBUS_TYPE_UINT32, &modem->signal_strength,
DBUS_TYPE_INVALID);
}
/**
* modem_get_signal_quality
* @modem: modem object
*
* Send a DBUS message to find the signal quality of a modem
*/
static void modem_get_signal_quality(struct modem_data *modem)
{
switch (modem->type) {
case MM_MODEM_TYPE_CDMA:
modem_dbus_send_message(modem,
MM_MODEM_CDMA_INTERFACE,
MM_MODEM_CDMA_METHOD_GETSIGNALQUALITY,
modem_get_signal_quality_reply,
ME_GET_SIGNALQUALITY_FAILED,
DBUS_TYPE_INVALID);
return;
case MM_MODEM_TYPE_GSM:
modem_dbus_send_message(
modem,
MM_MODEM_GSM_NETWORK_INTERFACE,
MM_MODEM_GSM_NETWORK_METHOD_GETSIGNALQUALITY,
modem_get_signal_quality_reply,
ME_GET_SIGNALQUALITY_FAILED,
DBUS_TYPE_INVALID);
return;
default:
_DBG_MODEMMGR("Unknown modem type %d", modem->type);
modem_handle_event(modem, ME_GET_SIGNALQUALITY_FAILED);
break;
}
}
typedef void (*prop_reply_fn)(DBusPendingCall *, void *);
static void modem_get_properties(struct modem_data *modem,
const char *interface,
prop_reply_fn reply_fn)
{
modem_dbus_send_message(modem,
MM_DBUS_PROPERTIES_INTERFACE, DBUS_METHOD_GETALL,
reply_fn, ME_GET_PROPERTIES_FAILED,
DBUS_TYPE_STRING, &interface,
DBUS_TYPE_INVALID);
}
static void modem_get_properties_reply(DBusPendingCall *call, void *user_data);
static void modem_get_gsm_properties_reply(DBusPendingCall *call, void *user_data);
/**
* modem_get_properties_task:
* @modem: modem object
*
* Send a DBUS message to retrieve several properties of a modem
*/
static void modem_get_properties_task(struct modem_data *modem)
{
modem_get_properties(modem,
MM_MODEM_INTERFACE,
modem_get_properties_reply);
}
static void modem_get_gsm_network_properties_task(struct modem_data *modem)
{
modem_get_properties(modem,
MM_MODEM_GSM_NETWORK_INTERFACE,
modem_get_gsm_properties_reply);
}
static void modem_get_gsm_card_properties_task(struct modem_data *modem)
{
if (modem->type == MM_MODEM_TYPE_GSM)
modem_get_properties(modem,
MM_MODEM_GSM_CARD_INTERFACE,
modem_get_gsm_properties_reply);
else
modem_task_run(modem);
}
static connman_bool_t check_property(const char *wanted_key,
const char *supplied_key,
DBusMessageIter *value,
gint target_type,
gint *error)
{
if (wanted_key == NULL || value == NULL || supplied_key == NULL ||
error == NULL) {
if (error != NULL) {
(*error)++;
}
CONNMAN_ERROR("Expected non-null value");
return FALSE;
}
if (*error || g_str_equal(wanted_key, supplied_key) == FALSE) {
return FALSE;
}
gint type = dbus_message_iter_get_arg_type(value);
if (type != target_type) {
CONNMAN_ERROR("[%s]: Expected type 0x%x, got 0x%x",
wanted_key,
target_type,
type);
(*error)++;
return FALSE;
}
return TRUE;
}
static connman_bool_t modem_dbus_extract_uint32(const char *wanted_key,
const char *supplied_key,
DBusMessageIter *value,
dbus_uint32_t *output,
gint *error)
{
if (check_property(wanted_key, supplied_key,
value, DBUS_TYPE_UINT32, error)) {
dbus_message_iter_get_basic(value, output);
return TRUE;
}
return FALSE;
}
static connman_bool_t modem_dbus_extract_uint16(const char *wanted_key,
const char *supplied_key,
DBusMessageIter *value,
dbus_uint16_t *output,
gint *error)
{
if (check_property(wanted_key, supplied_key,
value, DBUS_TYPE_UINT16, error)) {
dbus_message_iter_get_basic(value, output);
return TRUE;
}
return FALSE;
}
/**
* modem_dbus_extract_string:
* If the supplied key matches the wanted key, set output to the contents of
* value
*
* @wanted_key: Key that this call is attempting to extract
* @supplied_key: key corresponding to the value
* @value: DBusMessageIter pointing to received value
* @output: destination for the result. If NULL, return silently.
* Caller must g_free
* @error: pointer to error status. Return if nonzero
*/
static connman_bool_t modem_dbus_extract_string(const char *wanted_key,
const char *supplied_key,
DBusMessageIter *value,
char **output,
gint *error)
{
if (check_property(wanted_key, supplied_key,
value, DBUS_TYPE_STRING, error)) {
const char *by_reference;
dbus_message_iter_get_basic(value, &by_reference);
*output = g_strdup(by_reference);
if (*output == NULL) {
CONNMAN_ERROR("Out of memory");
(*error)++;
}
return TRUE;
}
return FALSE;
}
typedef gint (*property_handler)(struct modem_data *,
const char *,
DBusMessageIter *);
static enum connman_network_cellular_technology
mm_gsm_technology_to_connman_technology();
static gint handle_modem_property(struct modem_data *modem,
const char *key,
DBusMessageIter *value)
{
dbus_uint32_t modem_state = MM_MODEM_STATE_UNKNOWN;
gint extract_err = 0;
if (modem_dbus_extract_string(
"Device", key, value, &modem->device,
&extract_err))
return extract_err;
if (modem_dbus_extract_string(
"MasterDevice", key, value,
&modem->master_device, &extract_err))
return extract_err;
if (modem_dbus_extract_string(
"Driver", key, value, &modem->driver,
&extract_err))
return extract_err;
if (modem_dbus_extract_uint32(
"Type", key, value,
(guint *)&modem->type,
&extract_err))
return extract_err;
if (modem_dbus_extract_uint32(
"IpMethod", key, value,
(guint *)&modem->ip_method,
&extract_err))
return extract_err;
if (modem_dbus_extract_string(
"UnlockRequired", key, value,
&modem->gsm.lock.unlock_required, &extract_err))
return extract_err;
if (modem_dbus_extract_uint32(
"UnlockRetries", key, value,
&modem->gsm.lock.retries, &extract_err))
return extract_err;
if (modem_dbus_extract_uint32(
"State", key, value,
&modem_state, &extract_err)) {
if (modem_state == MM_MODEM_STATE_CONNECTED) {
_DBG_MODEMMGR("modem starts out in CONNECTED state");
modem->connect_needed = TRUE;
}
return extract_err;
}
return extract_err;
}
static gint handle_cdma_property(struct modem_data *modem,
const char *key,
DBusMessageIter *value)
{
gint extract_err = 0;
modem_dbus_extract_string("MEID", key, value, &modem->meid,
&extract_err);
return extract_err;
}
/**
* handle_gsm_property handles properties for both the
* Modem.Gsm.Network and Modem.Gsm.Card interfaces.
*/
static gint handle_gsm_property(struct modem_data *modem,
const char *key,
DBusMessageIter *value)
{
guint technology;
gint extract_err = 0;
dbus_uint32_t facility_locks;
if (modem_dbus_extract_uint32("AccessTechnology", key, value,
&technology, &extract_err)) {
modem->network_technology =
mm_gsm_technology_to_connman_technology(technology);
} else if (modem_dbus_extract_uint32("EnabledFacilityLocks", key, value,
&facility_locks, &extract_err)) {
modem->gsm.lock.enabled = (facility_locks & MM_MODEM_GSM_FACILITY_SIM) != 0;
}
return extract_err;
}
/**
* handle_get_properties_reply:
*
* Process the reply from the DBUS Get (property) call, using
* the supplied property handler function to look for a specific
* set of properties.
*/
static void handle_get_properties_reply(DBusPendingCall *call, void *user_data,
property_handler prop_handler)
{
struct modem_data *modem = user_data;
DBusMessage *reply;
DBusError error;
gint extract_err = 0;
enum modem_event event = ME_GET_PROPERTIES_FAILED;
DBusMessageIter array, dict;
gint type;
_DBG_MODEMMGR("modem %s", modem->dbus_path);
dbus_error_init(&error);
reply = modem_dbus_steal_reply(call);
if (reply == NULL) {
CONNMAN_ERROR("Failed steal reply");
goto done_no_reply;
}
if (dbus_set_error_from_message(&error, reply)) {
CONNMAN_ERROR("DBus Error: %s:%s",
error.name, error.message);
goto done;
}
if (dbus_message_iter_init(reply, &array) == FALSE) {
_DBG_MODEMMGR("Could not iterate on reply %s", modem->dbus_path);
goto done;
}
if ((type = dbus_message_iter_get_arg_type(&array)) != DBUS_TYPE_ARRAY) {
_DBG_MODEMMGR("Expected array, got %x", type);
goto done;
}
dbus_message_iter_recurse(&array, &dict);
while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY &&
extract_err == 0) {
DBusMessageIter entry, value;
const char *key;
dbus_message_iter_recurse(&dict, &entry);
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &value);
_DBG_MODEMMGR("Saw key: %s", key);
extract_err = prop_handler(modem, key, &value);
dbus_message_iter_next(&dict);
}
if (extract_err == 0) {
event = ME_GET_PROPERTIES_SUCCEEDED;
}
done:
dbus_message_unref(reply);
done_no_reply:
dbus_error_free(&error);
modem_handle_event(modem, event);
}
static void modem_get_properties_reply(DBusPendingCall *call, void *user_data)
{
handle_get_properties_reply(call, user_data, handle_modem_property);
}
void modem_get_gsm_properties_reply(DBusPendingCall *call, void *user_data)
{
handle_get_properties_reply(call, user_data, handle_gsm_property);
}
void modem_get_cdma_properties_reply(DBusPendingCall *call, void *user_data)
{
handle_get_properties_reply(call, user_data, handle_cdma_property);
}
static void modem_get_gsm_identifier_reply(DBusPendingCall *call, void *user_data);
static void request_gsm_identifier(struct modem_data *modem, const char *method)
{
_DBG_MODEMMGR("Requesting GSM ID \"%s\"", method);
modem_dbus_send_message(modem, MM_MODEM_GSM_CARD_INTERFACE, method,
modem_get_gsm_identifier_reply,
ME_GET_PROPERTIES_FAILED,
DBUS_TYPE_INVALID);
}
gboolean imei_needed(struct modem_data *modem)
{
return modem->imei == NULL;
}
gboolean imsi_needed(struct modem_data *modem)
{
return modem->imsi == NULL;
}
gboolean spn_needed(struct modem_data *modem)
{
return modem->gsm.spn == NULL;
}
gboolean msisdn_needed(struct modem_data *modem)
{
return TRUE;
}
static struct gsm_id_request *get_next_id_request(struct modem_data *modem)
{
struct gsm_id_request *id_request;
++modem->gsm.id_index;
id_request = &gsm_id_requests[modem->gsm.id_index];
while (id_request->name != NULL && !id_request->id_needed(modem)) {
++modem->gsm.id_index;
++id_request;
}
if (id_request->name == NULL)
return NULL;
return id_request;
}
/**
* modem_get_type_identifiers_task:
* @modem: modem object
*
* Send a DBUS message to retrieve the identifiers associated with a
* modem: MEID for CDMA, IMEI, IMSI, SPN, and MSISDN for GSM. Skip
* identifiers for which we already have a value (most likely because
* it was returned by the GetStatus method).
*/
static void modem_get_identifiers_task(struct modem_data *modem)
{
struct gsm_id_request *id_request;
switch (modem->type) {
case MM_MODEM_TYPE_CDMA:
if (modem->meid != NULL) {
if (modem->pending_task)
modem_task_run(modem);
return;
}
modem_get_properties(modem,
MM_MODEM_CDMA_INTERFACE,
modem_get_cdma_properties_reply);
break;
case MM_MODEM_TYPE_GSM:
modem->gsm.id_index = -1;
id_request = get_next_id_request(modem);
if (id_request == NULL) {
if (modem->pending_task)
modem_task_run(modem);
return;
}
/* Request the first identifier */
request_gsm_identifier(modem, id_request->method);
break;
default:
connman_warn("%s:%s() unknown modem type %d",
__FILE__, __FUNCTION__, modem->type);
if (modem->pending_task)
modem_task_run(modem);
break;
return;
}
}
void modem_get_gsm_identifier_reply(DBusPendingCall *call, void *user_data)
{
struct modem_data *modem = user_data;
DBusMessage *reply;
DBusError error;
char *identifier = NULL;
struct gsm_id_request *id_request;
_DBG_MODEMMGR("modem %s", modem->dbus_path);
dbus_error_init(&error);
reply = modem_dbus_steal_reply(call);
if (reply == NULL) {
CONNMAN_ERROR("Failed steal reply");
modem_task_run(modem);
return;
}
id_request = &gsm_id_requests[modem->gsm.id_index];
if (id_request->name == NULL) {
_DBG_MODEMMGR("no outstanding identifier request");
dbus_message_unref(reply);
modem_task_run(modem);
return;
}
if (dbus_set_error_from_message(&error, reply))
goto next_id;
if (dbus_message_get_args(reply, &error,
DBUS_TYPE_STRING,
&identifier,
DBUS_TYPE_INVALID) == FALSE) {
if (dbus_error_is_set(&error) == TRUE)
CONNMAN_ERROR("Error retrieving reply argument: %s:%s",
error.name, error.message);
else
CONNMAN_ERROR("Reply argument type is not STRING");
goto next_id;
}
if (identifier == NULL || strlen(identifier) == 0) {
_DBG_MODEMMGR("Request for GSM id %s returned null or empty string",
id_request->name);
} else if (g_str_equal(id_request->name, "imei")) {
g_free(modem->imei);
modem->imei = g_strdup(identifier);
connman_device_set_string(modem->cm_device, "Cellular.IMEI",
modem->imei);
} else if (g_str_equal(id_request->name, "imsi")) {
g_free(modem->imsi);
modem->imsi = g_strdup(identifier);
connman_device_set_string(modem->cm_device,
"Cellular.IMSI",
modem->imsi);
} else if (g_str_equal(id_request->name, "spn")) {
g_free(modem->gsm.spn);
modem->gsm.spn = g_strdup(identifier);
} else if (g_str_equal(id_request->name, "msisdn")) {
connman_device_set_string(modem->cm_device, "Cellular.MDN",
identifier);
} else {
connman_warn("%s:%s() Unhandled identifier request: %s",
__FILE__, __FUNCTION__, id_request->name);
}
next_id:
dbus_error_free(&error);
dbus_message_unref(reply);
id_request = get_next_id_request(modem);
if (id_request != NULL)
request_gsm_identifier(modem, id_request->method);
else {
set_gsm_provider(modem, modem->cm_device);
modem_task_run(modem);
}
}
static void modem_get_status_reply(DBusPendingCall *call, void *user_data);
/**
* modem_get_status_task:
* @modem: modem object
*
* Send a DBUS message to retrieve the status of a modem in the
* form of a dictionary.
*/
static void modem_get_status_task(struct modem_data *modem)
{
modem_dbus_send_message(modem,
MM_MODEM_SIMPLE_INTERFACE,
MM_MODEM_SIMPLE_METHOD_GETSTATUS,
modem_get_status_reply,
ME_GET_PROPERTIES_FAILED,
DBUS_TYPE_INVALID);
}
static gint handle_status_property(struct modem_data *modem,
const char *key,
DBusMessageIter *value)
{
dbus_uint32_t modem_state = MM_MODEM_STATE_UNKNOWN;
dbus_uint32_t mm_activation_state =
MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED;
char *carrier = NULL;
char *meid = NULL;
char *imei = NULL;
char *imsi = NULL;
char *esn = NULL;
char *min = NULL;
char *mdn = NULL;
char *firmware_revision = NULL;
char *olp = NULL;
char *usage_url = NULL;
dbus_uint16_t prl_version = 0;
gint extract_err = 0;
if (modem_dbus_extract_string(
"carrier", key, value, &carrier,
&extract_err)) {
modem->configured_carrier = g_strdup(carrier);
connman_device_set_string(modem->cm_device,
"Cellular.Carrier",
modem->configured_carrier);
if (modem->type == MM_MODEM_TYPE_CDMA) {
/*
* See previous about wondering what to use as
* the value for the home provider country for
* CDMA.
*/
struct connman_network_operator op = {
.name = modem->configured_carrier,
.code = "",
.country = "us"};
connman_device_set_home_provider(modem->cm_device, &op);
connman_device_set_string(modem->cm_device,
"Cellular.FirmwareImageName",
modem->configured_carrier);
}
} else if (modem_dbus_extract_string(
"meid", key, value, &meid,
&extract_err)) {
g_free(modem->meid);
modem->meid = g_strdup(meid);
connman_device_set_string(modem->cm_device,
"Cellular.MEID",
modem->meid);
} else if (modem_dbus_extract_string(
"imei", key, value, &imei,
&extract_err)) {
g_free(modem->imei);
modem->imei = g_strdup(imei);
connman_device_set_string(modem->cm_device,
"Cellular.IMEI",
modem->imei);
} else if (modem_dbus_extract_string(
"imsi", key, value, &imsi,
&extract_err)) {
g_free(modem->imsi);
modem->imsi = g_strdup(imsi);
connman_device_set_string(modem->cm_device,
"Cellular.IMSI",
modem->imsi);
if (modem->type == MM_MODEM_TYPE_GSM)
set_gsm_provider(modem, modem->cm_device);
} else if (modem_dbus_extract_string(
"esn", key, value, &esn,
&extract_err)) {
connman_device_set_string(modem->cm_device,
"Cellular.ESN", esn);
} else if (modem_dbus_extract_string(
"mdn", key, value, &mdn,
&extract_err)) {
connman_device_set_string(modem->cm_device,
"Cellular.MDN", mdn);
} else if (modem_dbus_extract_string(
"min", key, value, &min,
&extract_err)) {
connman_device_set_string(modem->cm_device,
"Cellular.MIN", min);
} else if (modem_dbus_extract_string(
"payment_url", key, value, &olp,
&extract_err)) {
g_free(modem->olp.url);
modem->olp.url = g_strdup(olp);
} else if (modem_dbus_extract_string(
"payment_url_method", key, value, &olp,
&extract_err)) {
g_free(modem->olp.method);
modem->olp.method = g_strdup(olp);
} else if (modem_dbus_extract_string(
"payment_url_postdata", key, value, &olp,
&extract_err)) {
g_free(modem->olp.postdata);
modem->olp.postdata = g_strdup(olp);
} else if (modem_dbus_extract_string(
"usage_url", key, value, &usage_url,
&extract_err)) {
g_free(modem->usage_url);
modem->usage_url = g_strdup(usage_url);
} else if (modem_dbus_extract_uint16(
"prl_version", key, value, &prl_version,
&extract_err)) {
connman_device_set_prl_version(modem->cm_device,
prl_version);
} else if (modem_dbus_extract_uint32(
"state", key, value, &modem_state,
&extract_err)) {
if (modem_state == MM_MODEM_STATE_CONNECTED) {
_DBG_MODEMMGR("modem starts out in CONNECTED state");
modem->connect_needed = TRUE;
}
} else if (modem_dbus_extract_uint32(
"activation_state", key, value,
&mm_activation_state, &extract_err)) {
modem->cdma.activation_state =
convert_activation_state(mm_activation_state);
} else if (modem_dbus_extract_string(
"firmware_revision", key, value, &firmware_revision,
&extract_err)) {
connman_device_set_string(modem->cm_device,
"Cellular.FirmwareRevision",
firmware_revision);
}
return extract_err;
}
/**
* modem_get_status_reply:
* @modem: modem object
*
* Process the reply from the DBUS GetStatus call
*/
static void modem_get_status_reply(DBusPendingCall *call, void *user_data)
{
handle_get_properties_reply(call, user_data, handle_status_property);
}
static void modem_get_info_reply(DBusPendingCall *call, void *user_data)
{
struct modem_data *modem = user_data;
DBusMessage *reply;
DBusError error;
char *manufacturer;
char *model_id;
char *hardware_revision;
DBusMessageIter structure, field;
enum modem_event event = ME_GETINFO_FAILED;
_DBG_MODEMMGR("modem %s", modem->dbus_path);
dbus_error_init(&error);
reply = modem_dbus_steal_reply(call);
if (reply == NULL) {
CONNMAN_ERROR("Failed steal reply");
goto done_no_reply;
}
if (dbus_set_error_from_message(&error, reply)) {
CONNMAN_ERROR("DBus Error: %s:%s",
error.name, error.message);
goto done;
}
if (dbus_message_iter_init(reply, &structure) == FALSE) {
_DBG_MODEMMGR("Could not iterate on reply %s", modem->dbus_path);
goto done;
}
gint type;
if ((type = dbus_message_iter_get_arg_type(&structure)) != DBUS_TYPE_STRUCT) {
_DBG_MODEMMGR("Expected struct, got %x", type);
goto done;
}
dbus_message_iter_recurse(&structure, &field);
if (dbus_message_iter_get_arg_type(&field) != DBUS_TYPE_STRING)
goto done;
dbus_message_iter_get_basic(&field, &manufacturer);
dbus_message_iter_next(&field);
if (dbus_message_iter_get_arg_type(&field) != DBUS_TYPE_STRING)
goto done;
dbus_message_iter_get_basic(&field, &model_id);
dbus_message_iter_next(&field);
if (dbus_message_iter_get_arg_type(&field) != DBUS_TYPE_STRING)
goto done;
dbus_message_iter_get_basic(&field, &hardware_revision);
dbus_message_iter_next(&field);
event = ME_GETINFO_SUCCEEDED;
connman_device_set_string(modem->cm_device,
"Cellular.Manufacturer",
manufacturer);
connman_device_set_string(modem->cm_device,
"Cellular.ModelID",
model_id);
connman_device_set_string(modem->cm_device,
"Cellular.HardwareRevision",
hardware_revision);
done:
dbus_error_free(&error);
dbus_message_unref(reply);
done_no_reply:
modem_handle_event(modem, event);
}
/**
* modem_get_info_task:
* @modem: modem object
*
* Send a DBUS message to retrieve the information about a modem.
* Returns three strings: manufacturer, model ID, and hardware revision.
*/
static void modem_get_info_task(struct modem_data *modem)
{
modem_dbus_send_message(modem,
MM_MODEM_INTERFACE,
MM_MODEM_METHOD_GETINFO,
modem_get_info_reply,
ME_GETINFO_FAILED,
DBUS_TYPE_INVALID);
}
static struct modem_method_callback *modem_create_method_callback(
struct modem_data *modem,
void *callback_data,
error_conversion_func error_func)
{
struct modem_method_callback *cb;
cb = g_new0(struct modem_method_callback, 1);
cb->modem = modem;
cb->cb_data = callback_data;
cb->convert_error = error_func;
return cb;
}
static int modem_device_require_pin(struct connman_device *device,
const char *pin, connman_bool_t require,
struct connman_dbus_method_callback *callback)
{
struct modem_method_callback *cb;
struct modem_data *modem = modem_device_find_modem(device, __func__);
int err;
if (modem == NULL) {
return -ENODEV;
}
cb = modem_create_method_callback(modem, callback, convert_error);
err = modem_dbus_send_message_with_pending(
modem, cb,
DEFAULT_TIMEOUT_MS,
MM_MODEM_GSM_CARD_INTERFACE,
MM_MODEM_GSM_CARD_METHOD_ENABLEPIN,
DBUS_TYPE_STRING, &pin,
DBUS_TYPE_BOOLEAN, &require,
DBUS_TYPE_INVALID);
if (err < 0)
g_free(cb);
return err;
}
static int modem_device_enter_pin(struct connman_device *device,
const char *pin,
struct connman_dbus_method_callback *callback)
{
struct modem_method_callback *cb;
struct modem_data *modem = modem_device_find_modem(device, __func__);
int err;
if (modem == NULL) {
return -ENODEV;
}
cb = modem_create_method_callback(modem, callback, convert_error);
err = modem_dbus_send_message_with_pending(
modem, cb,
DEFAULT_TIMEOUT_MS,
MM_MODEM_GSM_CARD_INTERFACE,
MM_MODEM_GSM_CARD_METHOD_SENDPIN,
DBUS_TYPE_STRING, &pin,
DBUS_TYPE_INVALID);
if (err < 0)
g_free(cb);
return err;
}
static int modem_device_unblock_pin(struct connman_device *device,
const char *unblock_code, const char *pin,
struct connman_dbus_method_callback *callback)
{
struct modem_method_callback *cb;
struct modem_data *modem = modem_device_find_modem(device, __func__);
int err;
if (modem == NULL) {
return -ENODEV;
}
cb = modem_create_method_callback(modem, callback, convert_error);
err = modem_dbus_send_message_with_pending(
modem, cb,
DEFAULT_TIMEOUT_MS,
MM_MODEM_GSM_CARD_INTERFACE,
MM_MODEM_GSM_CARD_METHOD_SENDPUK,
DBUS_TYPE_STRING, &unblock_code,
DBUS_TYPE_STRING, &pin,
DBUS_TYPE_INVALID);
if (err < 0)
g_free(cb);
return err;
}
static int modem_device_change_pin(struct connman_device *device,
const char *old_pin, const char *new_pin,
struct connman_dbus_method_callback *callback)
{
struct modem_method_callback *cb;
struct modem_data *modem = modem_device_find_modem(device, __func__);
int err;
if (modem == NULL) {
return -ENODEV;
}
cb = modem_create_method_callback(modem, callback, convert_error);
err = modem_dbus_send_message_with_pending(
modem, cb,
DEFAULT_TIMEOUT_MS,
MM_MODEM_GSM_CARD_INTERFACE,
MM_MODEM_GSM_CARD_METHOD_CHANGEPIN,
DBUS_TYPE_STRING, &old_pin,
DBUS_TYPE_STRING, &new_pin,
DBUS_TYPE_INVALID);
if (err < 0)
g_free(cb);
return err;
}
static const char *ThreeGppCodeToTechString[] = {