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[] = {
"GSM", "GSM Compact", "UMTS", "EDGE", "HSDPA", "HSUPA", "HSPA"
};
static const int kNumTechStrings =
sizeof(ThreeGppCodeToTechString) / sizeof(const char *);
static void handle_scan_reply(DBusPendingCall *call, void *user_data)
{
struct modem_data *modem = user_data;
DBusMessage *reply;
DBusError error;
DBusMessageIter array, subarray;
gint type;
GPtrArray *networks;
if (modem->cm_device == NULL)
return;
reply = modem_dbus_steal_reply(call);
if (reply == NULL) {
CONNMAN_ERROR("Failed steal reply");
return;
}
/*
* From here on, the UI will be notified of something, even if it's
* an empty list.
*/
networks = g_ptr_array_sized_new(4);
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, reply)) {
CONNMAN_ERROR("Error reply: %s:%s",
error.name, error.message);
dbus_error_free(&error);
goto out;
}
dbus_error_free(&error);
if (dbus_message_iter_init(reply, &array) == FALSE) {
CONNMAN_ERROR("Could not iterate on reply %s", modem->dbus_path);
goto out;
}
if ((type = dbus_message_iter_get_arg_type(&array)) != DBUS_TYPE_ARRAY) {
CONNMAN_ERROR("Expected array, got %x", type);
goto out;
}
g_ptr_array_set_free_func(networks, free_dict);
dbus_message_iter_recurse(&array, &subarray);
while (dbus_message_iter_get_arg_type(&subarray) == DBUS_TYPE_ARRAY) {
char *status = NULL;
char *network_id = NULL;
const char *long_name = NULL;
char *short_name = NULL;
char *access_tech = NULL;
DBusMessageIter dict;
GHashTable *props;
props = g_hash_table_new_full(g_str_hash, g_str_equal,
NULL, g_free);
dbus_message_iter_recurse(&subarray, &dict);
while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry;
const char *key;
dbus_message_iter_recurse(&dict, &entry);
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
if ((type = dbus_message_iter_get_arg_type(&entry)) != DBUS_TYPE_STRING) {
CONNMAN_ERROR("Expected string value with key"
"'%s', got %x", key, type);
goto out;
}
if (g_str_equal(key, "status") == TRUE)
dbus_message_iter_get_basic(&entry, &status);
else if (g_str_equal(key, "operator-num"))
dbus_message_iter_get_basic(&entry, &network_id);
else if (g_str_equal(key, "operator-long"))
dbus_message_iter_get_basic(&entry, &long_name);
else if (g_str_equal(key, "operator-short"))
dbus_message_iter_get_basic(&entry, &short_name);
else if (g_str_equal(key, "access-tech"))
dbus_message_iter_get_basic(&entry, &access_tech);
else
connman_warn("%s: unknown scan list property %s",
__func__, key);
dbus_message_iter_next(&dict);
}
dbus_message_iter_next(&subarray);
if (long_name == NULL && network_id != NULL) {
struct mobile_provider *provider =
mobile_provider_lookup_by_network(provider_db,
network_id);
if (provider != NULL)
long_name = mobile_provider_get_name(provider);
}
/*
* The numerical values for 'status' and 'access-tech'
* are from 3GPP TS 27.007 Section 7.3
*/
if (status != NULL) {
if (g_str_equal(status, "0"))
status = "unknown";
else if (g_str_equal(status, "1"))
status = "available";
else if (g_str_equal(status, "2"))
status = "current";
else if (g_str_equal(status, "3"))
status = "forbidden";
g_hash_table_insert(props, "status", g_strdup(status));
}
if (network_id != NULL)
g_hash_table_insert(props, "network_id",
g_strdup(network_id));
if (short_name != NULL)
g_hash_table_insert(props, "short_name",
g_strdup(short_name));
if (long_name != NULL)
g_hash_table_insert(props, "long_name",
g_strdup(long_name));
if (access_tech != NULL) {
const char *techstr = NULL;
char *endp;
errno = 0;
long code = strtol(access_tech, &endp, 0);
if (errno != ERANGE && endp != access_tech &&
code >= 0 && code < kNumTechStrings)
techstr = ThreeGppCodeToTechString[code];
if (techstr != NULL)
g_hash_table_insert(props, "technology",
g_strdup(techstr));
}
g_ptr_array_add(networks, props);
}
out:
dbus_message_unref(reply);
connman_device_set_found_networks(modem->cm_device, networks);
}
static int modem_device_scan(struct connman_device *device)
{
struct modem_data *modem = modem_device_find_modem(device, __func__);
if (modem == NULL)
return -ENODEV;
_DBG_MODEMMGR("modem %s", modem->dbus_path);
if (modem->type != MM_MODEM_TYPE_GSM)
return -EOPNOTSUPP;
return modem_dbus_send_message_with_timeout(modem,
MM_MODEM_GSM_NETWORK_INTERFACE,
MM_MODEM_GSM_NETWORK_METHOD_SCAN,
handle_scan_reply,
NETWORK_SCAN_TIMEOUT_MS,
ME_NO_EVENT,
DBUS_TYPE_INVALID);
}
static int modem_device_register(struct connman_device *device,
const char *network_id,
struct connman_dbus_method_callback *callback)
{
struct modem_data *modem = modem_device_find_modem(device, __func__);
struct modem_method_callback *cb;
if (modem == NULL)
return -ENODEV;
if (modem->type != MM_MODEM_TYPE_GSM)
return -EOPNOTSUPP;
_DBG_MODEMMGR("modem %s register on %s", modem->dbus_path,
network_id && *network_id ? network_id : "home network");
cb = modem_create_method_callback(modem, callback, convert_register_error);
return modem_dbus_send_message_with_pending(modem,
cb,
NETWORK_REGISTER_TIMEOUT_MS,
MM_MODEM_GSM_NETWORK_INTERFACE,
MM_MODEM_GSM_NETWORK_METHOD_REGISTER,
DBUS_TYPE_STRING,
&network_id,
DBUS_TYPE_INVALID);
}
static enum connman_network_cellular_roaming_state mm_cdma_roaming_to_connman_roaming(
guint mm_roaming_state)
{
switch (mm_roaming_state) {
case MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN:
case MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED:
return CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN;
case MM_MODEM_CDMA_REGISTRATION_STATE_HOME:
return CONNMAN_NETWORK_ROAMING_STATE_HOME;
case MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING:
return CONNMAN_NETWORK_ROAMING_STATE_ROAMING;
}
return CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN;
}
static enum connman_network_cellular_roaming_state mm_gsm_regstate_to_connman_roaming(
guint mm_gsm_regstate)
{
switch (mm_gsm_regstate) {
case MM_MODEM_GSM_NETWORK_REG_STATUS_HOME:
return CONNMAN_NETWORK_ROAMING_STATE_HOME;
case MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING:
return CONNMAN_NETWORK_ROAMING_STATE_ROAMING;
default:
return CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN;
}
}
static enum connman_network_cellular_technology mm_gsm_technology_to_connman_technology(
guint mm_technology) {
switch (mm_technology) {
case MM_MODEM_GSM_ACCESS_TECH_GSM:
case MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT:
return CONNMAN_NETWORK_TECHNOLOGY_GSM;
break;
case MM_MODEM_GSM_ACCESS_TECH_GPRS:
return CONNMAN_NETWORK_TECHNOLOGY_GPRS;
break;
case MM_MODEM_GSM_ACCESS_TECH_EDGE:
return CONNMAN_NETWORK_TECHNOLOGY_EDGE;
break;
case MM_MODEM_GSM_ACCESS_TECH_UMTS:
return CONNMAN_NETWORK_TECHNOLOGY_UMTS;
break;
case MM_MODEM_GSM_ACCESS_TECH_HSDPA:
case MM_MODEM_GSM_ACCESS_TECH_HSUPA:
case MM_MODEM_GSM_ACCESS_TECH_HSPA:
return CONNMAN_NETWORK_TECHNOLOGY_HSPA;
break;
case MM_MODEM_GSM_ACCESS_TECH_HSPA_PLUS:
return CONNMAN_NETWORK_TECHNOLOGY_HSPA_PLUS;
break;
}
return CONNMAN_NETWORK_TECHNOLOGY_UNKNOWN;
}
/**
* Return TRUE if the modem is registered on a network
*/
static int
modem_is_registered(struct modem_data *modem) {
return (modem->cdma._1x_registration_state !=
MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN ||
modem->cdma.evdo_registration_state !=
MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN ||
modem->gsm.registration_state ==
MM_MODEM_GSM_NETWORK_REG_STATUS_HOME ||
modem->gsm.registration_state ==
MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING
);
}
static void
modem_clear_registration_state(struct modem_data *modem) {
modem->cdma.evdo_registration_state =
MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
modem->cdma._1x_registration_state =
MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
modem->gsm.registration_state =
MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE;
}
/**
* Handle a change in the registration state. If the device was
* not registered but now is, create a connman_network and a
* connman_service. This causes the mobile data network to
* become visible to the UI.
*/
static int modem_registration_state_change(struct modem_data *modem)
{
struct connman_network *network = get_network(modem);
_DBG_MODEMMGR("network %p", network);
if (modem_is_registered(modem)) {
if (modem->state == MODEM_STATE_ENABLED) {
/*
* Once connected the modem could change the type of
* registration and still stay connected.
*/
modem->state = MODEM_STATE_REGISTERED;
}
if (network == NULL) {
network = create_network(modem);
}
if (network == NULL) {
return -ENODEV;
}
if (modem->cdma.evdo_registration_state !=
MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) {
connman_network_set_string(network,
"Network Type",
"CDMA: EVDO");
connman_network_set_registration_info(
network,
CONNMAN_NETWORK_TECHNOLOGY_EVDO,
mm_cdma_roaming_to_connman_roaming(
modem->cdma.evdo_registration_state));
} else if (modem->cdma._1x_registration_state !=
MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) {
connman_network_set_string(network,
"Network Type",
"CDMA: 1x");
connman_network_set_registration_info(
network,
CONNMAN_NETWORK_TECHNOLOGY_1XRTT,
mm_cdma_roaming_to_connman_roaming(
modem->cdma._1x_registration_state));
} else {
connman_network_set_string(network,
"Network Type",
"GSM");
connman_network_set_registration_info(
network,
modem->network_technology,
mm_gsm_regstate_to_connman_roaming(
modem->gsm.registration_state));
if (modem->state == MODEM_STATE_CONNECTED &&
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__);
return modem_task_run_new(modem,
&disconnect_modem_work);
return 0;
}
}
connman_network_set_available(network, TRUE);
connman_network_set_scangen(network, ++modem->scangen);
connman_network_set_strength(network, modem->signal_strength);
modem_get_signal_quality(modem);
} else {
if (network != NULL) {
_DBG_MODEMMGR("Removing network %s",
modem->network_dbus_name);
connman_device_remove_network(modem->cm_device,
modem->network_dbus_name);
}
if (modem->state == MODEM_STATE_CONNECTED ||
modem->state == MODEM_STATE_REGISTERED) {
modem->state = MODEM_STATE_ENABLED;
}
}
return 0;
}
/**
* modem_set_connected
*
* Handle a change in the connected state of the modem. Once the
* modem is connected, the network should become active, and if
* necessary, flimflam should start dhcp or ppp.
*/
static int modem_set_connected(struct modem_data *modem, gboolean connected)
{
struct connman_network *network = get_network(modem);
int index = connman_inet_ifindex(modem->device);
_DBG_MODEMMGR("connected: %d", connected);
if (network == NULL) {
_DBG_MODEMMGR("No network");
switch(modem->state) {
case MODEM_STATE_REGISTERED:
case MODEM_STATE_CONNECTED:
modem->state = MODEM_STATE_ENABLED;
break;
default:
break;
}
return -ENODEV;
}
if (connected) {
/* for modems using ppp index will be -1 */
if (index != -1)
connman_inet_ifup(index);
modem->state = MODEM_STATE_CONNECTED;
connman_device_set_connected(modem->cm_device, TRUE);
connman_network_set_connected(network);
modem_record_connect_info(modem);
} else {
if (modem->state == MODEM_STATE_CONNECTED) {
modem->state = MODEM_STATE_REGISTERED;
}
connman_network_set_disconnected(network);
/* for modems using ppp index will be -1 */
if (index != -1)
connman_inet_ifdown(index);
}
return 0;
}
static connman_bool_t can_retry_connect(struct modem_data *modem)
{
return modem->type == MM_MODEM_TYPE_GSM &&
modem->gsm.connect_tries < MAX_CONNECT_TRIES;
}
static void set_next_apn(struct modem_data *modem, char *apn, char *username,
char *password)
{
struct connman_network_apn *next_apn = &modem->gsm.next_apn;
g_free(next_apn->apn);
g_free(next_apn->network_id);
g_free(next_apn->username);
g_free(next_apn->password);
next_apn->apn = g_strdup(apn);
next_apn->username = g_strdup(username);
next_apn->password = g_strdup(password);
if (apn == NULL)
next_apn->network_id = NULL;
else
next_apn->network_id = g_strdup(modem->gsm.network_id);
}
static void next_apn_try_state(struct modem_data *modem)
{
struct connman_network *network;
const struct mobile_apn *apn;
const struct connman_network_apn *user_apn = NULL;
const struct connman_network_apn *saved_apn = NULL;
_DBG_MODEMMGR("APN try state %d", modem->gsm.apn_try_state);
network = connman_device_get_network(modem->cm_device,
modem->network_dbus_name);
switch (modem->gsm.apn_try_state) {
case APN_TRY_START:
modem->gsm.apn_try_state = APN_TRY_SAVED;
saved_apn = connman_network_get_last_good_apn(network);
if (saved_apn != NULL) {
_DBG_MODEMMGR("Trying last good APN %s",
saved_apn->apn);
set_next_apn(modem, saved_apn->apn, saved_apn->username,
saved_apn->password);
break;
}
/* ... fall through ... */
case APN_TRY_SAVED:
modem->gsm.apn_try_state = APN_TRY_USER_APN;
if (network != NULL)
user_apn = connman_network_get_apn(network);
if (user_apn != NULL) {
_DBG_MODEMMGR("Trying user-supplied APN %s",
user_apn->apn);
set_next_apn(modem, user_apn->apn, user_apn->username,
user_apn->password);
break;
}
/* ... fall through ... */
case APN_TRY_USER_APN:
modem->gsm.apn_try_state = APN_TRY_LIST;
modem->gsm.apn_index = 0;
/* ... fall through ... */
case APN_TRY_LIST:
if (modem->gsm.provider != NULL &&
modem->gsm.apn_index < modem->gsm.provider->num_apns) {
apn = modem->gsm.provider->apns[modem->gsm.apn_index++];
set_next_apn(modem, apn->value, apn->username, apn->password);
_DBG_MODEMMGR("From APN list for %s: %s",
mobile_provider_get_name(modem->gsm.provider),
apn->value);
break;
}
modem->gsm.apn_try_state = APN_TRY_NULL_APN;
_DBG_MODEMMGR("Trying null APN");
set_next_apn(modem, NULL, NULL, NULL);
break;
case APN_TRY_NULL_APN:
_DBG_MODEMMGR("No more APNs to try, failing");
modem->gsm.apn_try_state = APN_TRY_FAILED;
break;
case APN_TRY_FAILED:
/* should never get here */
connman_warn("%s: tried to advance beyond APN_TRY_FAILED state",
__func__);
break;
}
}
static void reset_connect_state(struct modem_data *modem)
{
if (modem->type == MM_MODEM_TYPE_GSM) {
modem->gsm.connect_tries = 0;
modem->gsm.apn_try_state = APN_TRY_START;
next_apn_try_state(modem);
}
}
/**
* modem_connect_failed
*
* Handle a failure to connect to the modem
*/
static int modem_connect_failed(struct modem_data *modem, connman_bool_t retry)
{
struct connman_network *network = get_network(modem);
_DBG_MODEMMGR("retry=%d tries so far=%d", retry,
modem->gsm.connect_tries);
if (network != NULL) {
connman_network_save_last_good_apn(network, NULL);
if (retry && can_retry_connect(modem)) {
next_apn_try_state(modem);
if (modem->gsm.apn_try_state != APN_TRY_FAILED) {
/*
* Make sure the top-level connect timer gets
* reset every time we retry a connection
* attempt, e.g., with a new APN.
*/
connman_network_restart_connect_timer(network);
modem_connect_start(modem);
return 0;
}
}
}
modem_set_connected(modem, FALSE);
if (network == NULL) {
_DBG_MODEMMGR("No network");
return -ENODEV;
}
connman_network_set_error(network,
CONNMAN_ELEMENT_ERROR_CONNECT_FAILED);
return 0;
}
/**
* modem_set_powered
*
* Handle a change in the powered state of the modem.
*/
static void modem_set_powered(struct modem_data *modem, gboolean powered)
{
connman_device_set_powered(modem->cm_device, powered);
}
/*
* Indicate an enable or disable failure.
*/
static void modem_set_powered_failed(struct modem_data *modem,
enum connman_element_error error)
{
connman_device_set_powered_failed(modem->cm_device, error);
}
/**
* Handle a change in the signal quality
*/
static void modem_update_signal_quality(struct modem_data *modem)
{
struct connman_network *network = get_network(modem);
if (network != NULL) {
connman_network_set_scangen(network, ++modem->scangen);
connman_network_set_strength(network, modem->signal_strength);
}
}
/**
* Implementation of the modem state machine
*
*/
static void modem_handle_event(struct modem_data *modem,
enum modem_event event)
{
const char *event_name = eventToString(event);
const char *state_name = stateToString(modem->state);
_DBG_MODEMMGR("modem %p state %s event %s", modem, state_name, event_name);
switch (modem->state) {
case MODEM_STATE_UNINITIALIZED:
switch (event) {
case ME_ENABLE_SUCCEEDED:
case ME_DISABLE_SUCCEEDED:
case ME_GETINFO_SUCCEEDED:
case ME_GET_PROPERTIES_SUCCEEDED:
modem_task_run(modem);
return;
case ME_ENABLE_FAILED:
case ME_DISABLE_FAILED:
case ME_GETINFO_FAILED:
case ME_GET_PROPERTIES_FAILED:
case ME_CREATE_DEVICE_FAILED:
modem->state = MODEM_STATE_FAILED;
modem_task_completed(&modem);
return;
default:
break;
}
_DBG_MODEMMGR("Invalid event %s for state UNINITIALIZED",
event_name);
return;
case MODEM_STATE_DISABLED:
switch (event) {
case ME_REGISTRATION_STATE_CHANGED:
if (modem->pending_task == FALSE) {
/* N.B. may transition to REGISTERED */
modem_registration_state_change(modem);
}
return;
case ME_ENABLE_SUCCEEDED:
modem->state = MODEM_STATE_ENABLED;
modem_task_run(modem);
return;
case ME_ENABLE_FAILED:
modem_task_completed(&modem);
return;
case ME_CREATE_DEVICE_FAILED:
modem->state = MODEM_STATE_FAILED;
return;
default:
break;
}
_DBG_MODEMMGR("Invalid event %s for state %s\n",
event_name, state_name);
return;
case MODEM_STATE_ENABLED:
switch (event) {
case ME_REGISTRATION_STATE_CHANGED:
/*
* If a task is pending, then we are really in
* the process of enabling the modem.
* Suppress the transition to REGISTERED until
* the enabling task has completed. This
* prevents connman from trying to autoconnect
* before we have finished enabling.
*/
if (modem->pending_task == FALSE) {
/* N.B. may transition to REGISTERED */
modem_registration_state_change(modem);
}
return;
case ME_DISABLE_SUCCEEDED:
modem_set_powered(modem, FALSE);
modem->state = MODEM_STATE_DISABLED;
modem_task_run(modem);
return;
case ME_DISABLE_FAILED:
modem_set_powered(modem, TRUE);
modem_task_completed(&modem);
return;
case ME_REGISTER_SUCCEEDED:
/*
* Do not change state yet,
* wait for ME_GET_REGISTRATION_STATE_SUCCEEDED
*/
modem_task_run(modem);
return;
case ME_REGISTER_FAILED:
/* Clear the selected network, if any. If there was
* a selected network, try to register on the home
* network. */
if (connman_device_get_selected_network(modem->cm_device)) {
connman_device_clear_selected_network(modem->cm_device);
modem_task_rerun(modem);
return;
}
modem->gsm.registration_state =
MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE;
modem_registration_state_change(modem);
/* run the rest of the tasks even if not registered */
modem_task_run(modem);
return;
case ME_GET_REGISTRATION_STATE_SUCCEEDED:
/*
* Complete the enable task first. Then
* process the registration change, which will
* create a connman network object and may
* auto connect.
*/
modem_task_run(modem); /* process modem_report_enabled_task */
/* N.B. may transition to REGISTERED */
if (modem != NULL)
modem_registration_state_change(modem);
return;
case ME_GET_REGISTRATION_STATE_FAILED:
modem_task_completed(&modem);
return;
case ME_GET_SIGNALQUALITY_FAILED:
return;
case ME_GET_SIGNALQUALITY_SUCCEEDED:
case ME_SIGNAL_QUALITY_CHANGED:
modem_update_signal_quality(modem);
return;
case ME_GETINFO_SUCCEEDED:
case ME_GETINFO_FAILED:
case ME_GET_PROPERTIES_SUCCEEDED:
case ME_GET_PROPERTIES_FAILED:
modem_task_run(modem);
return;
default:
break;
}
_DBG_MODEMMGR("Invalid event %s for state ENABLED", event_name);
return;
case MODEM_STATE_REGISTERED:
switch (event) {
case ME_REGISTRATION_STATE_CHANGED:
modem_registration_state_change(modem);
return;
case ME_DISABLE_SUCCEEDED:
modem_set_powered(modem, FALSE);
modem->state = MODEM_STATE_DISABLED;
modem_clear_registration_state(modem);
modem_registration_state_change(modem);
modem_task_run(modem);
return;
case ME_DISABLE_FAILED:
modem_set_powered(modem, TRUE);
modem_task_completed(&modem);
return;
case ME_GET_REGISTRATION_STATE_SUCCEEDED:
/*
* Complete the enable task first. Then
* process the registration change, which will
* create a connman network object and may
* auto connect.
*/
modem_task_run(modem); /* process modem_report_enabled_task */
if (modem != NULL)
modem_registration_state_change(modem);
return;
case ME_GET_REGISTRATION_STATE_FAILED:
modem_task_completed(&modem);
return;
case ME_GET_SIGNALQUALITY_FAILED:
/* N.B. We can continue even without signal quality */
return;
case ME_GET_SIGNALQUALITY_SUCCEEDED:
case ME_SIGNAL_QUALITY_CHANGED:
modem_update_signal_quality(modem);
return;
case ME_GETINFO_SUCCEEDED:
case ME_GETINFO_FAILED:
case ME_GET_PROPERTIES_SUCCEEDED:
case ME_GET_PROPERTIES_FAILED:
modem_task_run(modem);
return;
default:
break;
}
_DBG_MODEMMGR("Invalid event %s for state REGISTERED", event_name);
return;
case MODEM_STATE_CONNECTED:
switch (event) {
case ME_REGISTRATION_STATE_CHANGED:
modem_registration_state_change(modem);
return;
case ME_DISCONNECT_SUCCEEDED:
/*
* May or may not transition through the
* DISCONNECTING state before getting to
* DISCONNECTED
*/
modem_set_connected(modem, TRUE);
modem->state = MODEM_STATE_REGISTERED;
modem_set_connected(modem, FALSE);
modem_task_run(modem);
return;
case ME_DISCONNECT_FAILED:
modem_task_completed(&modem);
return;
case ME_DISABLE_SUCCEEDED:
handle_reported_disconnect(modem);
modem_set_powered(modem, FALSE);
modem->state = MODEM_STATE_DISABLED;
modem_clear_registration_state(modem);
modem_registration_state_change(modem);
modem_task_run(modem);
return;
case ME_DISABLE_FAILED:
modem_set_powered(modem, TRUE);
modem_task_completed(&modem);
return;
case ME_GETIP4CONFIG_SUCCEEDED:
case ME_GETIP4CONFIG_FAILED:
/* TODO(jglasgow): implement.... */
modem_task_run(modem);
break;
case ME_GET_SIGNALQUALITY_FAILED:
/* N.B. We can continue even without signal quality */
return;
case ME_GET_SIGNALQUALITY_SUCCEEDED:
case ME_SIGNAL_QUALITY_CHANGED:
modem_update_signal_quality(modem);
return;
default:
break;
}
_DBG_MODEMMGR("Invalid event %s for state CONNECTED\n", event_name);
return;
case MODEM_STATE_FAILED:
_DBG_MODEMMGR("Invalid event %s for state FAILED", event_name);
return;
case MODEM_STATE_REMOVED:
_DBG_MODEMMGR("Ignoring event %s for state REMOVED", event_name);
return;
default:
_DBG_MODEMMGR("Invalid state %s", state_name);
}
}
static const char *eventToString(enum modem_event event)
{
static char text[32];
switch (event) {
case ME_NO_EVENT: return "NO_EVENT";
case ME_PROPERTIES_CHANGED: return "PROPERTIES_CHANGED";
case ME_SIGNAL_QUALITY_CHANGED: return "SIGNAL_QUALITY_CHANGED";
case ME_REGISTRATION_STATE_CHANGED: return "REGISTRATION_STATE_CHANGED";
case ME_ENABLE_SUCCEEDED: return "ENABLE_SUCCEEDED";
case ME_ENABLE_FAILED: return "ENABLE_FAILED";
case ME_REGISTER_SUCCEEDED: return "REGISTER_SUCCEEDED";
case ME_REGISTER_FAILED: return "REGISTER_FAILED";
case ME_DISABLE_SUCCEEDED: return "DISABLE_SUCCEEDED";
case ME_DISABLE_FAILED: return "DISABLE_FAILED";
case ME_GETIP4CONFIG_SUCCEEDED: return "GETIP4CONFIG_SUCCEEDED";
case ME_GETIP4CONFIG_FAILED: return "GETIP4CONFIG_FAILED";
case ME_GETINFO_SUCCEEDED: return "GETINFO_SUCCEEDED";
case ME_GETINFO_FAILED: return "GETINFO_FAILED";
case ME_GET_PROPERTIES_SUCCEEDED: return "GET_PROPERTIES_SUCCEEDED";
case ME_GET_PROPERTIES_FAILED: return "GET_PROPERTIES_FAILED";
case ME_DISCONNECT_SUCCEEDED: return "DISCONNECT_SUCCEEDED";
case ME_DISCONNECT_FAILED: return "DISCONNECT_FAILED";
case ME_GET_SIGNALQUALITY_SUCCEEDED: return "GET_SIGNALQUALITY_SUCCEEDED";
case ME_GET_SIGNALQUALITY_FAILED: return "GET_SIGNALQUALITY_FAILED";
case ME_GET_SERVING_SYSTEM_SUCCEEDED: return "GET_SERVING_SYSTEM_SUCCEEDED";
case ME_GET_SERVING_SYSTEM_FAILED: return "GET_SERVING_SYSTEM_FAILED";
case ME_GET_REGISTRATION_STATE_SUCCEEDED: return "GET_REGISTRATION_STATE_SUCCEEDED";
case ME_GET_REGISTRATION_STATE_FAILED: return "GET_REGISTRATION_STATE_FAILED";
case ME_CREATE_DEVICE_FAILED: return "CREATE_DEVICE_FAILED";
default:
g_snprintf(text, sizeof(text), "Unknown event %d", event);
return text;
}
}
static const char *stateToString(enum modem_state state)
{
static char text[32];
switch (state) {
case MODEM_STATE_UNINITIALIZED: return "UNINITIALIZED";
case MODEM_STATE_DISABLED: return "DISABLED";
case MODEM_STATE_ENABLED: return "ENABLED";
case MODEM_STATE_REGISTERED: return "REGISTERED";
case MODEM_STATE_CONNECTED: return "CONNECTED";
case MODEM_STATE_FAILED: return "FAILED";
case MODEM_STATE_REMOVED: return "REMOVED";
default:
g_snprintf(text, sizeof(text), "Unknown state %d", state);
return text;
}
}
static enum connman_network_activation_state convert_activation_state(
guint mm_activation_state)
{
switch (mm_activation_state) {
case MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED:
return CONNMAN_NETWORK_ACTIVATION_STATE_NOT_ACTIVATED;
case MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING:
return CONNMAN_NETWORK_ACTIVATION_STATE_ACTIVATING;
case MM_MODEM_CDMA_ACTIVATION_STATE_PARTIALLY_ACTIVATED:
return CONNMAN_NETWORK_ACTIVATION_STATE_PARTIALLY_ACTIVATED;
case MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED:
return CONNMAN_NETWORK_ACTIVATION_STATE_ACTIVATED;
default:
return CONNMAN_NETWORK_ACTIVATION_STATE_NOT_ACTIVATED;
}
}
static enum connman_element_error convert_activation_error(
guint mm_activation_error)
{
switch (mm_activation_error) {
case MM_MODEM_CDMA_ACTIVATION_ERROR_WRONG_RADIO_INTERFACE:
return CONNMAN_ELEMENT_ERROR_NEED_EVDO;
case MM_MODEM_CDMA_ACTIVATION_ERROR_ROAMING:
return CONNMAN_ELEMENT_ERROR_NEED_HOME_NETWORK;
case MM_MODEM_CDMA_ACTIVATION_ERROR_COULD_NOT_CONNECT:
case MM_MODEM_CDMA_ACTIVATION_ERROR_SECURITY_AUTHENTICATION_FAILED:
case MM_MODEM_CDMA_ACTIVATION_ERROR_PROVISIONING_FAILED:
return CONNMAN_ELEMENT_ERROR_OTASP_FAILED;
case MM_MODEM_CDMA_ACTIVATION_ERROR_NO_ERROR:
return CONNMAN_ELEMENT_ERROR_NO_ERROR;
case MM_MODEM_CDMA_ACTIVATION_ERROR_NO_SIGNAL:
default:
return CONNMAN_ELEMENT_ERROR_ACTIVATION_FAILED;
}
}
/**
* add_modem
* @path: dbus path to the modem
*
* Add a modem to the list of modems, and then send a dbus message to
* find the modem properties. Once the properties are known, we will
* create a connman_device to associate with the modem data.
*
*/
static void add_modem(const char *owner, const char *service, const char *path)
{
struct modem_data *modem = NULL;
_DBG_MODEMMGR("adding device path %s owner %s", path, owner);
modem = g_hash_table_lookup(modem_hash, path);
if (modem != NULL) {
_DBG_MODEMMGR("found existing modem with path %s", path);
return;
}
modem = g_try_new0(struct modem_data, 1);
if (modem == NULL) {
_DBG_MODEMMGR("failed to allocate modem for path %s", path);
return;
}
modem->owner = g_strdup(owner);
if (modem->owner == NULL) {
_DBG_MODEMMGR("failed to allocate owner for modem %s", path);
g_free(modem);
return;
}
modem->service = g_strdup(service);
if (modem->service == NULL) {
_DBG_MODEMMGR("failed to allocate service for modem %s", path);
g_free(modem->owner);
g_free(modem);
return;
}
modem->dbus_path = g_strdup(path);
if (modem->dbus_path == NULL) {
_DBG_MODEMMGR("failed to allocate path for modem %s", path);
g_free(modem->service);
g_free(modem->owner);
g_free(modem);
return;
}
/* TODO(jglasgow): read profile to get phone_number, handle ENOMEM */
modem->type = 0;
modem->state = MODEM_STATE_UNINITIALIZED;
modem->signal_strength = 0;
modem->network_technology = CONNMAN_NETWORK_TECHNOLOGY_UNKNOWN;
/* Set IP method to an invalid type */
modem->ip_method = -1;
/* Update the list of devices */
g_hash_table_insert(modem_hash, modem->dbus_path, modem);
/* collect modem properties */
modem_task_run_new(modem, &initialize_modem_work);
_DBG_MODEMMGR("added device modem %p", modem);
}
/**
* Callback from modem_hash on device removal
*/
static void remove_modem(gpointer data)
{
struct modem_data *modem = data;
_DBG_MODEMMGR("removing device modem %p", modem);
/* cleanup and delete a modem device object */
if (modem->disconnect_needed) {
modem->disconnect_needed = FALSE;
handle_reported_disconnect(modem);
}
if (modem->cm_device != NULL) {
connman_device_set_powered(modem->cm_device, FALSE);
connman_device_unregister(modem->cm_device);
connman_device_unref(modem->cm_device);
}
modem_dbus_cancel_pending_calls(modem);
g_free(modem->owner);
g_free(modem->service);
g_free(modem->dbus_path);
g_free(modem->phone_number);
g_free(modem->olp.url);
g_free(modem->olp.method);
g_free(modem->olp.postdata);
g_free(modem->usage_url);
g_free(modem->device);
g_free(modem->driver);
g_free(modem->master_device);
g_free(modem->network_dbus_name);
g_free(modem->configured_carrier);
g_free(modem->gsm.operator_name);
g_free(modem->gsm.operator_country);
g_free(modem->gsm.network_id);
g_free(modem->gsm.next_apn.apn);
g_free(modem->gsm.next_apn.network_id);
g_free(modem->gsm.next_apn.username);
g_free(modem->gsm.next_apn.password);
g_free(modem->meid);
g_free(modem->imei);
g_free(modem->imsi);
g_free(modem);
}
/**
* remove_device:
* @path: dbus path to the device
*
* Remove a device from the list of modem devices
*/
static void remove_device(const char *path)
{
struct modem_data *modem;
_DBG_MODEMMGR("removing device path %s", path);
modem = g_hash_table_lookup(modem_hash, path);
if (modem == NULL) {
_DBG_MODEMMGR("cannot remove modem with path %s", path);
return;
}
if (modem->pending_task) {
/* A pending task means there are outstanding DBUS
* messages that have a pointer to the modem object.
* Mark the object to be removed later.
*/
modem->task.remove_on_complete = TRUE;
} else {
if (modem->state == MODEM_STATE_CONNECTED)
modem->disconnect_needed = TRUE;
modem->state = MODEM_STATE_REMOVED;
/* remove from list of devices causing its destruction */
g_hash_table_remove(modem_hash, path);
}
}
/* ===================== */
/* Network object for 3G */
/* ===================== */
static struct modem_data *network_get_modem(struct connman_network *network,
const char *func)
{
struct connman_device *device;
device = connman_network_get_device(network);
if (device == NULL) {
return NULL;
}
return modem_device_find_modem(device, func);
}
static int network_probe(struct connman_network *network)
{
const char *path;
path = connman_network_get_string(network, "DBus.Object");
_DBG_MODEMMGR("network %p path %s", network, path);
return 0;
}
static int network_connect(struct connman_network *network)
{
int err;
struct modem_data *modem;
enum connman_network_cellular_roaming_state roaming_state;
_DBG_MODEMMGR("network %p", network);
modem = network_get_modem(network, __func__);
if (modem == NULL) {
connman_network_set_disconnected(network);
return -ENODEV;
}
if (modem->state == MODEM_STATE_CONNECTED) {
return -EISCONN;
}
roaming_state = connman_network_get_roaming_state(network);
if (!connman_device_roaming_allowed(modem->cm_device) &&
roaming_state == CONNMAN_NETWORK_ROAMING_STATE_ROAMING) {
connman_error("%s: failing connect: "
"roaming connections not allowed",
__func__);
return -ENONET;
}
reset_connect_state(modem);
err = modem_connect_start(modem);
if (err != -EINPROGRESS) {
connman_network_set_disconnected(network);
return -EIO;
}
return -EINPROGRESS;
}
static int network_disconnect(struct connman_network *network)
{
struct modem_data *modem;
_DBG_MODEMMGR("network %p index %d", network,
connman_network_get_index(network));
modem = network_get_modem(network, __func__);
if (modem == NULL) {
return -ENODEV;
}
if (modem->state != MODEM_STATE_CONNECTED) {
return -EALREADY;
}
return modem_task_run_new(modem, &disconnect_modem_work);
}
static void network_remove(struct connman_network *network)
{
_DBG_MODEMMGR("network %p", network);
}
/**
* Activate the modem on the network.
*
*/
static int modem_network_activate(struct connman_network *network,
const char *carrier)
{
struct modem_data *modem;
DBusMessage *message;
modem = network_get_modem(network, __func__);
if (modem == NULL) {
return -ENODEV;
}
if (modem->type != MM_MODEM_TYPE_CDMA) {
_DBG_MODEMMGR("Cannot activate non-CDMA modem\n");
return -EINVAL;
}
switch (modem->state) {
case MODEM_STATE_ENABLED:
case MODEM_STATE_REGISTERED:
break;
default:
_DBG_MODEMMGR("Cannot activate in state: %s\n",
stateToString(modem->state));
return -EIO;
}
if (modem_dbus_build_message(modem->owner, MM_MODEM_CDMA_INTERFACE,
modem->dbus_path, &message,
MM_MODEM_CDMA_METHOD_ACTIVATE) < 0) {
_DBG_MODEMMGR("activate modem %s", modem->dbus_path);
return -EIO;
}
_DBG_MODEMMGR("activate modem %s", modem->dbus_path);
dbus_message_append_args(message,
DBUS_TYPE_STRING, &carrier,
DBUS_TYPE_INVALID);
if (modem_dbus_send_with_reply(modem,
message,
modem,
modem_activate_reply,
ACTIVATE_TIMEOUT_MS) < 0) {
_DBG_MODEMMGR("activate modem %s", modem->dbus_path);
return -EIO;
}
modem_update_activation_state(modem,
CONNMAN_NETWORK_ACTIVATION_STATE_ACTIVATING,
CONNMAN_ELEMENT_ERROR_NO_ERROR);
return 0;
}
static struct connman_network_driver network_driver = {
.name = "network",
.type = CONNMAN_NETWORK_TYPE_CELLULAR,
.probe = network_probe,
.remove = network_remove,
.connect = network_connect,
.disconnect = network_disconnect,
/* .setup = network_setup, upstream */
.activate_cellular_network = modem_network_activate,
};
/*==========================================================
* Handlers for modem manager root object calls and signals
*==========================================================*/
static void enumerate_devices_reply(DBusPendingCall *call,
void *user_data)
{
struct modem_manager *manager = (struct modem_manager *)user_data;
DBusMessage *reply;
DBusError error;
char **results;
int i, num_results;
const char *sender;
_DBG_MODEMMGR("enumerate_devices_reply: %p", call);
reply = dbus_pending_call_steal_reply(call);
if (reply == NULL) {
CONNMAN_ERROR("Failed steal reply");
return;
}
dbus_error_init(&error);
sender = dbus_message_get_sender(reply);
if (dbus_set_error_from_message(&error, reply)) {
CONNMAN_ERROR("DBus Error: %s:%s",
error.name, error.message);
goto done;
}
/*
* Since this is the first message we receive from a modem manager,
* record the unique bus name associated with the manager, so that
* we can later tell which modem manager owns a given modem.
*/
if (manager->bus_name == NULL)
manager->bus_name = g_strdup(sender);
if (dbus_message_get_args(reply, &error,
DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH,
&results, &num_results,
DBUS_TYPE_INVALID) == FALSE) {
if (dbus_error_is_set(&error) == TRUE) {
CONNMAN_ERROR("Unpacking error: %s:%s",
error.name, error.message);
} else {
CONNMAN_ERROR("Wrong arguments for enumerate_devices");
}
goto done;
}
if (num_results == 0) {
_DBG_MODEMMGR("no modems returned");
goto done;
}
for (i = 0; i < num_results; i++) {
/* Add device will remove duplicates */
add_modem(sender, manager->service, results[i]);
}
/* TODO(jglasgow): consider removing devices that no longer exist */
g_strfreev(results);
done:
dbus_error_free(&error);
dbus_message_unref(reply);
}
static void cdma_registration_state_changed(
struct modem_data *modem,
DBusMessage *msg)
{
DBusMessageIter iter;
dbus_message_iter_init(msg, &iter);
dbus_message_iter_get_basic(&iter, &modem->cdma._1x_registration_state);
dbus_message_iter_next(&iter);
dbus_message_iter_get_basic(&iter, &modem->cdma.evdo_registration_state);
dbus_message_iter_next(&iter);
modem_handle_event(modem, ME_REGISTRATION_STATE_CHANGED);
}
static void signal_quality_changed(
struct modem_data *modem,
DBusMessage *msg)
{
DBusMessageIter iter;
dbus_message_iter_init(msg, &iter);
dbus_message_iter_get_basic(&iter, &modem->signal_strength);
dbus_message_iter_next(&iter);
modem_handle_event(modem, ME_SIGNAL_QUALITY_CHANGED);
}
static void network_mode_changed(
struct modem_data *modem,
DBusMessage *msg)
{
DBusMessageIter iter;
int network_mode;
dbus_message_iter_init(msg, &iter);
dbus_message_iter_get_basic(&iter, &network_mode);
dbus_message_iter_next(&iter);
_DBG_MODEMMGR("%s: network_mode = 0x%x",
modem->dbus_path, network_mode);
/* TODO(jglasgow): add as a property of the network */
}
static void gsm_registration_state_changed(
struct modem_data *modem,
DBusMessage *msg)
{
DBusMessageIter iter;
const char *network_id, *operator_name;
dbus_message_iter_init(msg, &iter);
dbus_message_iter_get_basic(&iter, &modem->gsm.registration_state);
dbus_message_iter_next(&iter);
dbus_message_iter_get_basic(&iter, &network_id);
dbus_message_iter_next(&iter);
dbus_message_iter_get_basic(&iter, &operator_name);
g_free(modem->gsm.operator_name);
modem->gsm.operator_name = g_strdup(operator_name);
set_network_id(modem, network_id);
dbus_message_iter_next(&iter);
modem_handle_event(modem, ME_REGISTRATION_STATE_CHANGED);
}
/**
* Update flimflam state after the modem manager has told us that
* the modem is in the connected state. If we're roaming, and
* data roaming is not being allowed, forcefully disconnect,
* after getting our state in sync with the modem manager's.
*/
static void handle_reported_connect(struct modem_data *modem)
{
struct connman_network *network = get_network(modem);
modem->connect_needed = FALSE;
if (network != NULL && connman_network_get_connected(network) == FALSE) {
modem_set_connected(modem, TRUE);
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);
}
}
}
/**
* Update flimflam state after the modem manager has told us that
* the modem is in the disconnected state.
*/
static void handle_reported_disconnect(struct modem_data *modem)
{
struct connman_network *network = get_network(modem);
if (network != NULL) {
connman_network_set_disconnecting(network);
modem_set_connected(modem, FALSE);
}
}
static void modem_state_changed(struct modem_data *modem, DBusMessage *msg) {
DBusMessageIter iter;
dbus_uint32_t old_state;
dbus_uint32_t new_state;
dbus_uint32_t reason = MM_MODEM_STATE_CHANGED_REASON_UNKNOWN;
dbus_message_iter_init(msg, &iter);
dbus_message_iter_get_basic(&iter, &old_state);
dbus_message_iter_next(&iter);
dbus_message_iter_get_basic(&iter, &new_state);
dbus_message_iter_next(&iter);
if (old_state == new_state) {
/* Warn on state transitions that should not be sent */
CONNMAN_ERROR("Invalid state_change %d -> %d",
old_state, new_state);
return;
}
if (new_state == MM_MODEM_STATE_REGISTERED ||
new_state == MM_MODEM_STATE_ENABLED)
dbus_message_iter_get_basic(&iter, &reason);
_DBG_MODEMMGR("%u --> %u: %u", old_state, new_state, reason);
switch (new_state) {
case MM_MODEM_STATE_DISABLED:
/*
* This state change may be the modem manager
* updating us on the status of an Disable operation
* that we initiated, in which case the call to
* modem_device_disable() will be a no-op, and will
* result in a message being printed when debug
* logging is turned on.
*/
modem_handle_event(modem, ME_DISABLE_SUCCEEDED);
break;
case MM_MODEM_STATE_ENABLED:
/*
* This state change may be the modem manager
* updating us on the status of an Enable operation
* that we initiated, in which case the call to
* modem_device_enable() will be a no-op and will
* result in a message being printed when debug
* logging is turned on.
*/
if (old_state == MM_MODEM_STATE_DISABLED ||
old_state == MM_MODEM_STATE_ENABLING) {
modem_device_enable(modem->cm_device);
break;
}
/* ... fall into */
case MM_MODEM_STATE_REGISTERED:
/*
* Check the old state to avoid treating
* enable->register transitions as a disconnect
*/
if (old_state == MM_MODEM_STATE_CONNECTED ||
old_state == MM_MODEM_STATE_DISCONNECTING) {
handle_reported_disconnect(modem);
if (modem->pending_task)
modem_task_run(modem);
} else if (old_state == MM_MODEM_STATE_REGISTERED) {
/* Because of check above, new_state must be ENABLED */
modem->gsm.registration_state =
MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE;
modem_registration_state_change(modem);
}
break;
case MM_MODEM_STATE_SEARCHING:
if (old_state == MM_MODEM_STATE_REGISTERED) {
modem->gsm.registration_state =
MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING;
modem_registration_state_change(modem);
} else if (old_state == MM_MODEM_STATE_ENABLING) {
/* Sometimes modem-manager will go straight from
ENABLING to SEARCHING; handle that as we handle
the transition from ENABLING to ENABLED. (We
don't do anything special for the ENABLED to
SEARCHING transition.) */
modem_device_enable(modem->cm_device);
}
break;
case MM_MODEM_STATE_CONNECTED:
/* No need to check old state */
handle_reported_connect(modem);
if (modem->pending_task)
modem_task_run(modem);
break;
default:
CONNMAN_ERROR("Unhandled state_change %d -> %d",
old_state, new_state);
}
}
static void modem_activation_state_changed(struct modem_data *modem,
DBusMessage *msg) {
DBusMessageIter iter;
dbus_uint32_t mm_state;
dbus_uint32_t mm_error;
char *min = NULL;
char *mdn = NULL;
char *olp = NULL;
gint extract_err = 0;
gint type;
DBusMessageIter dict;
connman_bool_t olp_changed = FALSE;
dbus_message_iter_init(msg, &iter);
dbus_message_iter_get_basic(&iter, &mm_state);
dbus_message_iter_next(&iter);
dbus_message_iter_get_basic(&iter, &mm_error);
dbus_message_iter_next(&iter);
_DBG_MODEMMGR("state = %u error = %u", mm_state, mm_error);
if ((type = dbus_message_iter_get_arg_type(&iter)) != DBUS_TYPE_ARRAY) {
_DBG_MODEMMGR("Expected array, got %x", type);
goto update;
}
dbus_message_iter_recurse(&iter, &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);
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);
olp_changed = TRUE;
} 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);
olp_changed = TRUE;
} 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);
olp_changed = TRUE;
}
dbus_message_iter_next(&dict);
}
if (olp_changed) {
struct connman_network *network = get_network(modem);
if (network != NULL)
connman_network_set_olp_url(network,
modem->olp.url,
modem->olp.method,
modem->olp.postdata);
}
update:
modem_update_activation_state(modem,
convert_activation_state(mm_state),
convert_activation_error(mm_error));
}
static void mm_properties_changed(struct modem_data *modem,
DBusMessage *msg)
{
DBusMessageIter iter;
DBusMessageIter dict;
char *interface;
guint technology;
dbus_uint32_t facility_locks = 0;
connman_bool_t saw_unlock_props = FALSE;
struct connman_network *network = get_network(modem);
dbus_message_iter_init(msg, &iter);
dbus_message_iter_get_basic(&iter, &interface);
dbus_message_iter_next(&iter);
_DBG_MODEMMGR("MmPropertiesChanged for interface %s", interface);
gint type;
if ((type = dbus_message_iter_get_arg_type(&iter)) != DBUS_TYPE_ARRAY) {
_DBG_MODEMMGR("Expected array, got %x", type);
return;
}
dbus_message_iter_recurse(&iter, &dict);
gint extract_err = 0;
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 property key: %s", key);
if (modem_dbus_extract_uint32("UnlockRetries", key,
&value, &modem->gsm.lock.retries,
&extract_err))
saw_unlock_props = TRUE;
else if (modem_dbus_extract_string("UnlockRequired", key,
&value, &modem->gsm.lock.unlock_required,
&extract_err))
saw_unlock_props = TRUE;
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;
saw_unlock_props = TRUE;
} else if (network != NULL &&
modem_dbus_extract_uint32("AccessTechnology", key,
&value, &technology, &extract_err)) {
_DBG_MODEMMGR(" value: %u", technology);
enum connman_network_cellular_technology tech =
mm_gsm_technology_to_connman_technology(technology);
connman_network_set_registration_info(
network, tech,
CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN);
}
dbus_message_iter_next(&dict);
}
if (saw_unlock_props && modem->cm_device != NULL)
connman_device_set_unlock_properties(modem->cm_device,
modem->gsm.lock.unlock_required,
modem->gsm.lock.retries,
modem->gsm.lock.enabled);
}
static DBusHandlerResult modemmgr_filter(DBusConnection *conn,
DBusMessage *msg,
void *data)
{
const char *member, *path, *interface;
struct modem_data *modem;
/*
* Bail out quickly if the message is not one in which
* we are interested.
*/
interface = dbus_message_get_interface(msg);
if (interface == NULL)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
/* The signal that has been sent */
member = dbus_message_get_member(msg);
if (member == NULL) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (!g_str_has_prefix(interface, MM_MODEMMANAGER_INTERFACE) &&
!g_str_equal(member, MM_MANAGER_SIGNAL_MMPROPERTIESCHANGED)) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
/* The path of the device (sending the message) */
path = dbus_message_get_path(msg);
if (path == NULL) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
_DBG_MODEMMGR("path %s member %s", path, member);
if (g_strcmp0(interface, MM_MODEMMANAGER_INTERFACE) == 0) {
/* TODO(jglasgow): make sure type is correct */
DBusMessageIter iter;
const char *modem_path;
const char *sender = dbus_message_get_sender(msg);
if (sender == NULL) {
_DBG_MODEMMGR("unknown sender");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
dbus_message_iter_init(msg, &iter);
dbus_message_iter_get_basic(&iter, &modem_path);
dbus_message_iter_next(&iter);
_DBG_MODEMMGR("member %s path %s modem path %s sender %s",
member, path, modem_path, sender);
if (g_str_equal(member, MM_MANAGER_SIGNAL_DEVICEADDED)) {
/* Convert the manager root object name to a service name. */
char *service = g_strdup(path+1);
/* Remove trailing '/'. */
int len = strlen(service);
if (len > 0 && service[len-1] == '.')
service[len-1] = '\0';
/* Map '/' to '.'. */
char *p = strchr(service, '/');
while (p) {
*p = '.';
p = strchr(p+1, '/');
}
add_modem(sender, service, modem_path);
g_free(service);
} else if (g_str_equal(member,
MM_MANAGER_SIGNAL_DEVICEREMOVED)) {
remove_device(modem_path);
} else {
_DBG_MODEMMGR("unknown signal %s", member);
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
/* Signals on the other interfaces must correspond to a modem object */
modem = g_hash_table_lookup(modem_hash, path);
if (modem == NULL) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (g_strcmp0(interface, MM_MODEM_INTERFACE) == 0) {
if (g_str_equal(member,
MM_MODEM_SIGNAL_STATECHANGED)) {
modem_state_changed(modem, msg);
}
} else if (g_strcmp0(interface, MM_MODEM_CDMA_INTERFACE) == 0) {
if (g_str_equal(member,
MM_MODEM_CDMA_SIGNAL_REGISTRATIONSTATECHANGED))
cdma_registration_state_changed(modem, msg);
else if (g_str_equal(member,
MM_MODEM_CDMA_SIGNAL_SIGNALQUALITY))
signal_quality_changed(modem, msg);
else if (g_str_equal(member,
MM_MODEM_CDMA_SIGNAL_ACTIVATIONSTATECHANGED))
modem_activation_state_changed(modem, msg);
} else if (g_strcmp0(interface,
MM_MODEM_GSM_NETWORK_INTERFACE) == 0) {
if (g_str_equal(member,
MM_MODEM_GSM_NETWORK_SIGNAL_REGISTRATIONINFO))
gsm_registration_state_changed(modem, msg);
else if (g_str_equal(member,
MM_MODEM_GSM_NETWORK_SIGNAL_SIGNALQUALITY))
signal_quality_changed(modem, msg);
else if (g_str_equal(member,
MM_MODEM_GSM_NETWORK_SIGNAL_NETWORKMODE))
network_mode_changed(modem, msg);
} else if (g_strcmp0(interface,
MM_DBUS_PROPERTIES_INTERFACE) == 0) {
if (g_str_equal(member, MM_MANAGER_SIGNAL_MMPROPERTIESCHANGED)) {
mm_properties_changed(modem, msg);
}
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static const char *modemmgr_rule =
"type=signal,interface=" MM_MODEMMANAGER_INTERFACE;
static const char *modem_rule = "type=signal,interface=" MM_MODEM_INTERFACE;
static const char *cdma_rule = "type=signal,interface=" MM_MODEM_CDMA_INTERFACE;
static const char *gsm_network_rule =
"type=signal,interface=" MM_MODEM_GSM_NETWORK_INTERFACE;
static const char *mm_properties_rule =
"type=signal,interface=" MM_DBUS_PROPERTIES_INTERFACE
",member=MmPropertiesChanged";
/**
* modemmgr_connect: Handle a new connection from modem manager
*
* Set up filters to get signals from modem manager. Enumerate the
* modems.
*/
static void modemmgr_connect(DBusConnection *connection, void *user_data)
{
struct modem_manager *manager = (struct modem_manager *)user_data;
DBusMessage *message;
DBusPendingCall *call;
_DBG_MODEMMGR("service %s", manager->service);
/* Enumerate the devices (modems) */
message = dbus_message_new_method_call(
manager->service,
manager->path,
MM_MODEMMANAGER_INTERFACE,
MM_MANAGER_METHOD_ENUMERATEDEVICES);
if (message == NULL)
return;
if (dbus_connection_send_with_reply(connection, message,
&call, DEFAULT_TIMEOUT_MS) == FALSE) {
CONNMAN_ERROR("Failed to get modem devices");
goto done;
}
if (call == NULL) {
CONNMAN_ERROR("D-Bus connection not available");
goto done;
}
dbus_pending_call_set_notify(call, enumerate_devices_reply,
manager, NULL);
dbus_pending_call_unref(call);
done:
dbus_message_unref(message);
}
static void maybe_remove_modem(gpointer key,
gpointer value,
gpointer user_data)
{
struct modem_data *modem = value;
char *bus_name = user_data;
_DBG_MODEMMGR("modem %s busname %s owner %s", (char *)key, bus_name,
modem->owner);
if (bus_name != NULL && g_strcmp0(modem->owner, bus_name) == 0) {
if (modem->pending_task)
modem->task.remove_on_complete = TRUE;
else
remove_device((const char *)key);
}
}
static void modemmgr_disconnect(DBusConnection *connection, void *user_data)
{
struct modem_manager *manager = (struct modem_manager *)user_data;
_DBG_MODEMMGR("service %s", manager->service);
/*
* A modem manager disappeared. Remove all modems that the
* manager was tracking.
*/
g_hash_table_foreach(modem_hash, maybe_remove_modem, manager->bus_name);
g_free(manager->bus_name);
manager->bus_name = NULL;
}
/**
* modem_manager_new
*
* Create a new modem_manager object which stores the service name and
* path used to communicate with a modem manager dbus server.
*
* Returns:
* pointer to initialized modem_manager object or NULL
*/
static struct modem_manager *modem_manager_new(const char *service,
const char *path)
{
struct modem_manager *manager = g_try_new0(struct modem_manager, 1);
if (manager == NULL) {
return NULL;
}
manager->service = g_strdup(service);
if (manager->service == NULL) {
g_free(manager);
return NULL;
}
manager->path = g_strdup(path);
if (manager->path == NULL) {
g_free(manager->service);
g_free(manager);
return NULL;
}
return manager;
}
/**
* modem_manager_free
*
* release all resources associated with a modem_manager object
*/
static void modem_manager_free(void *user_data)
{
struct modem_manager *manager = (struct modem_manager *)user_data;
g_free(manager->service);
g_free(manager->path);
g_free(manager->bus_name);
g_free(manager);
}
/**
* modemmgr_watch_mm_services
*
* set up dbus callback functions to handle the appearance and
* disappearance of modem manager
*
* Args:
* managers: comma separated list of service:path values
*/
static int modemmgr_watch_mm_services(const char *managers)
{
char **services = g_strsplit(managers, ",", 0);
int ii;
for(ii = 0; services && services[ii] ; ii++) {
char *service = services[ii];
char *path;
struct modem_manager *manager;
path = strchr(service, ':');
if (path == NULL) {
_DBG_MODEMMGR("Service %s missing path", service);
continue;
}
*path = '\0';
path++;
manager = modem_manager_new(service, path);
if (manager == NULL) {
_DBG_MODEMMGR("Could not create manager: %s", service);
continue;
}
manager->watch = g_dbus_add_service_watch(connection,
manager->service,
modemmgr_connect,
modemmgr_disconnect,
manager, NULL);
if (manager->watch != 0) {
g_hash_table_insert(modem_manager_watches,
manager->service, manager);
} else {
_DBG_MODEMMGR("Could not add service watch for %s",
service);
modem_manager_free(manager);
}
}
g_strfreev(services);
return 0;
}
static void modemmgr_unwatch_service(gpointer key,
gpointer value,
gpointer userdata)
{
struct modem_manager *manager = (struct modem_manager *)value;
g_dbus_remove_watch(connection, manager->watch);
}
static void modemmgr_exit(void);
/**
* modemmgr_init: Initialize the modem manager plugin.
*
* Set up a dbus connection to talk to modem managers
* Set up a service watch to know when a modem manager starts and stops
* Set up filters to receive signals from modem managers
*/
static int modemmgr_init(void)
{
int err;
connection = connman_dbus_get_connection();
if (connection == NULL) {
return -EIO;
}
/* We supply a free function for the value, but not the key,
* because the key is part of the value structure.
*/
modem_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
NULL, remove_modem);
if (modem_hash == NULL) {
dbus_connection_unref(connection);
return -EIO;
}
modem_manager_watches = g_hash_table_new_full(g_str_hash, g_str_equal,
NULL,
modem_manager_free);
if (modem_manager_watches == NULL) {
goto remove;
}
if (dbus_connection_add_filter(connection,
modemmgr_filter, NULL, NULL) == FALSE) {
_DBG_MODEMMGR("Could not add filter to connection %p",
connection);
goto remove;
}
if (dbus_pending_call_allocate_data_slot(
&modem_pending_call_slot) == FALSE) {
_DBG_MODEMMGR("Could not allocate pending call slot");
goto remove;
}
/* TODO(jglasgow): add error handling on the dbus_connection_flush */
dbus_bus_add_match(connection, modemmgr_rule, NULL);
dbus_bus_add_match(connection, modem_rule, NULL);
dbus_bus_add_match(connection, cdma_rule, NULL);
dbus_bus_add_match(connection, gsm_network_rule, NULL);
dbus_bus_add_match(connection, mm_properties_rule, NULL);
dbus_connection_flush(connection);
/* watch for connections from modem managers */
modem_managers = connman_option_get_string("modemmanagers");
if (modem_managers == NULL) {
modem_managers = kDefaultMMSearchPath;
}
modemmgr_watch_mm_services(modem_managers);
/* Register our drivers */
err = connman_network_driver_register(&network_driver);
if (err < 0)
goto remove;
err = connman_device_driver_register(&modem_driver);
if (err < 0) {
connman_network_driver_unregister(&network_driver);
goto remove;
}
provider_db = mobile_provider_open_db(PROVIDERDB);
if (provider_db == NULL)
connman_warn("%s:%s() Cannot load mobile provider database: %s",
__FILE__, __FUNCTION__, strerror(errno));
return 0;
remove:
modemmgr_exit();
return -EIO;
}
/**
* modemmgr_exit: Clean up the modem manager plug in.
*/
static void modemmgr_exit(void)
{
if (connection == NULL) {
return;
}
if (modem_hash != NULL) {
g_hash_table_destroy(modem_hash);
modem_hash = NULL;
}
if (provider_db != NULL) {
mobile_provider_close_db(provider_db);
provider_db = NULL;
}
if (modem_manager_watches != NULL) {
g_hash_table_foreach(modem_manager_watches,
modemmgr_unwatch_service, 0);
g_hash_table_destroy(modem_manager_watches);
modem_manager_watches = NULL;
}
if (modem_pending_call_slot != -1)
dbus_pending_call_free_data_slot(&modem_pending_call_slot);
dbus_bus_remove_match(connection, modemmgr_rule, NULL);
dbus_bus_remove_match(connection, modem_rule, NULL);
dbus_bus_remove_match(connection, cdma_rule, NULL);
dbus_bus_remove_match(connection, gsm_network_rule, NULL);
dbus_bus_remove_match(connection, mm_properties_rule, NULL);
dbus_connection_remove_filter(connection, modemmgr_filter, NULL);
dbus_connection_unref(connection);
connection = NULL;
connman_device_driver_unregister(&modem_driver);
connman_network_driver_unregister(&network_driver);
}
CONNMAN_PLUGIN_DEFINE(modemmgr, "Modem Manager plugin", VERSION,
CONNMAN_PLUGIN_PRIORITY_DEFAULT,
modemmgr_init, modemmgr_exit)