| /* |
| * |
| * 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) |