blob: c2297997bf40e45a2b90141791bc265f21a8345b [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (C) 2007-2009 Intel Corporation. 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 <stdio.h>
#include <errno.h>
#include <string.h>
#include <glib.h>
#define CONNMAN_API_SUBJECT_TO_CHANGE
#include <connman/plugin.h>
#include <connman/device.h>
#include <connman/inet.h>
#include <connman/log.h>
#include <WiMaxAPI.h>
#include <WiMaxAPIEx.h>
#include "iwmx.h"
/*
* Connman plugin interface
*
* This part deals with the connman internals
*/
/* WiMAX network driver probe/remove, nops */
static int iwmx_cm_network_probe(struct connman_network *nw)
{
return 0;
}
static void iwmx_cm_network_remove(struct connman_network *nw)
{
}
/*
* Called by connman when it wants us to tell the device to connect to
* the network @network_el; the device is @network_el->parent.
*
* We do a synchronous call to start the connection; the logic
* attached to the status change callback will update the connman
* internals once the change happens.
*/
static int iwmx_cm_network_connect(struct connman_network *nw)
{
int result;
struct wmxsdk *wmxsdk;
const char *station_name = connman_network_get_identifier(nw);
wmxsdk = connman_device_get_data(connman_network_get_device(nw));
result = iwmx_sdk_connect(wmxsdk, nw);
_DBG_IWMX("(nw %p [%s] wmxsdk %p) = %d\n", nw, station_name, wmxsdk, result);
return result;
}
/*
* Called by connman to have the device @nw->parent
* disconnected from @nw.
*
* We do a synchronous call to start the disconnection; the logic
* attached to the status change callback will update the connman
* internals once the change happens.
*/
static int iwmx_cm_network_disconnect(struct connman_network *nw)
{
int result;
struct wmxsdk *wmxsdk;
const char *station_name = connman_network_get_identifier(nw);
wmxsdk = connman_device_get_data(connman_network_get_device(nw));
result = iwmx_sdk_disconnect(wmxsdk);
_DBG_IWMX("(nw %p [%s] wmxsdk %p) = %d\n", nw, station_name, wmxsdk, result);
return 0;
}
/*
* "Driver" for the networks detected by a device.
*/
static struct connman_network_driver iwmx_cm_network_driver = {
.name = "iwmx",
.type = CONNMAN_NETWORK_TYPE_WIMAX,
.probe = iwmx_cm_network_probe,
.remove = iwmx_cm_network_remove,
.connect = iwmx_cm_network_connect,
.disconnect = iwmx_cm_network_disconnect,
};
/*
* A (maybe) new network is available, create/update its data
*
* If the network is new, we create and register a new element; if it
* is not, we reuse the one in the list.
*
* NOTE:
* wmxsdk->network_mutex has to be locked
*/
struct connman_network *__iwmx_cm_network_available(
struct wmxsdk *wmxsdk, const char *station_name,
const char *station_type,
const void *sdk_nspname, size_t sdk_nspname_size,
int strength)
{
struct connman_network *nw = NULL;
struct connman_device *dev = wmxsdk->dev;
char group[3 * strlen(station_name) + 1];
unsigned cnt;
nw = connman_device_get_network(dev, station_name);
if (nw == NULL) {
_DBG_IWMX("new network %s", station_name);
nw = connman_network_create(station_name,
CONNMAN_NETWORK_TYPE_WIMAX);
connman_network_set_index(nw, connman_device_get_index(dev));
connman_network_set_protocol(nw, CONNMAN_NETWORK_PROTOCOL_IP);
connman_network_set_name(nw, station_name);
connman_network_set_blob(nw, "WiMAX.NSP.name",
sdk_nspname, sdk_nspname_size);
/* FIXME: add roaming info? */
/* Set the group name -- this has to be a unique
* [a-zA-Z0-9_] string common to all the networks that
* are actually the same provider. In WiMAX each
* network from the CAPI is a single provider, so we
* just set this as the network name, encoded in
* hex. */
for (cnt = 0; station_name[cnt] != 0; cnt++)
sprintf(group + 3 * cnt, "%02x", station_name[cnt]);
group[3 * cnt + 1] = 0;
connman_network_set_group(nw, station_name);
if (connman_device_add_network(dev, nw) < 0) {
connman_network_unref(nw);
goto error_add;
}
} else
_DBG_IWMX("updating network %s nw %p\n", station_name, nw);
connman_network_set_available(nw, TRUE);
connman_network_set_scangen(nw, connman_network_get_scangen(nw)+1);
connman_network_set_strength(nw, strength);
connman_network_set_string(nw, "WiMAX Network Type", station_type);
error_add:
return nw;
}
/*
* A new network is available [locking version]
*
* See __iwmx_cm_network_available() for docs
*/
struct connman_network *iwmx_cm_network_available(
struct wmxsdk *wmxsdk, const char *station_name,
const char *station_type,
const void *sdk_nspname, size_t sdk_nspname_size,
int strength)
{
struct connman_network *nw;
g_static_mutex_lock(&wmxsdk->network_mutex);
nw = __iwmx_cm_network_available(wmxsdk, station_name, station_type,
sdk_nspname, sdk_nspname_size,
strength);
g_static_mutex_unlock(&wmxsdk->network_mutex);
return nw;
}
/*
* The device has been enabled, make sure connman knows
*/
static void iwmx_cm_dev_enabled(struct wmxsdk *wmxsdk)
{
struct connman_device *dev = wmxsdk->dev;
connman_inet_ifup(connman_device_get_index(dev));
connman_device_set_powered(dev, TRUE);
}
/*
* The device has been disabled, make sure connman is aware of it.
*/
static void iwmx_cm_dev_disabled(struct wmxsdk *wmxsdk)
{
struct connman_device *dev = wmxsdk->dev;
connman_inet_ifdown(connman_device_get_index(dev));
connman_device_set_powered(dev, FALSE);
}
/*
* The device has been (externally to connman) connnected to a
* network, make sure connman knows.
*
* When the device is connected to a network, this function is called
* to change connman's internal state to reflect the fact.
*
* If the change came from an external entity, that means that our
* connect code wasn't called. Our connect code sets
* @wmxsdk->connecting_nw to the network we were connecting
* to. If it is unset, it means an external entity forced the device
* to connect. In that case, we need to find out which network it was
* connected to, and create/lookup a @nw for it.
*
* Once the nw is set, then we are done.
*/
static void iwmx_cm_dev_connected(struct wmxsdk *wmxsdk)
{
struct connman_network *nw;
g_mutex_lock(wmxsdk->connect_mutex);
nw = wmxsdk->connecting_nw;
if (nw == NULL) {
nw = __iwmx_sdk_get_connected_network(wmxsdk);
if (nw == NULL) {
connman_error("wmxsdk: can't find connected network\n");
goto error_nw_find;
}
}
wmxsdk->nw = connman_network_ref(nw);
wmxsdk->connecting_nw = NULL;
connman_network_set_connected(nw, TRUE);
_DBG_IWMX("connected to network %s\n",
connman_network_get_identifier(nw));
error_nw_find:
g_mutex_unlock(wmxsdk->connect_mutex);
}
/*
* The device has been (externally to connman) disconnnected, make
* sure connman knows
*
* We need to reverse the steps done in iwmx_cm_dev_connected().
* If the event was caused by an external entity and we had no record
* of being connected to a network...well, bad luck. We'll just
* pretend it happened ok.
*/
static void __iwmx_cm_dev_disconnected(struct wmxsdk *wmxsdk)
{
struct connman_network *nw = wmxsdk->nw;
if (nw != NULL) {
_DBG_IWMX("disconnected from network %s\n",
connman_network_get_identifier(nw));
connman_network_set_connected(nw, FALSE);
connman_network_unref(nw);
wmxsdk->nw = NULL;
} else
_DBG_IWMX("disconnected from unknown network\n");
}
/*
* The device has been disconnnected, make sure connman knows
*
* See __iwmx_cm_dev_disconnect() for more information.
*/
static void iwmx_cm_dev_disconnected(struct wmxsdk *wmxsdk)
{
g_mutex_lock(wmxsdk->connect_mutex);
__iwmx_cm_dev_disconnected(wmxsdk);
g_mutex_unlock(wmxsdk->connect_mutex);
}
/*
* Handle a change in state
*
* This is were most of the action happens. When the device changes
* state, this will catch it (through the state change callback or an
* explicit call) and call iwmx_cm_dev_*ed() to indicate to connman what
* happened.
*
* Finally, cache the new device status.
*/
void __iwmx_cm_state_change(struct wmxsdk *wmxsdk,
WIMAX_API_DEVICE_STATUS __new_status)
{
WIMAX_API_DEVICE_STATUS __old_status = wmxsdk->status;
WIMAX_API_DEVICE_STATUS old_status;
WIMAX_API_DEVICE_STATUS new_status;
/*
* Simplify state transition computations.
*
* For practical effects, some states are the same
*/
/* Conection_Idle is the same as Data_Connected */
if (__old_status == WIMAX_API_DEVICE_STATUS_Connection_Idle)
old_status = WIMAX_API_DEVICE_STATUS_Data_Connected;
else
old_status = __old_status;
if (__new_status == WIMAX_API_DEVICE_STATUS_Connection_Idle)
new_status = WIMAX_API_DEVICE_STATUS_Data_Connected;
else
new_status = __new_status;
/* Radio off: all are just RF_OFF_SW (the highest) */
switch (__old_status) {
case WIMAX_API_DEVICE_STATUS_RF_OFF_HW_SW:
case WIMAX_API_DEVICE_STATUS_RF_OFF_HW:
case WIMAX_API_DEVICE_STATUS_RF_OFF_SW:
old_status = WIMAX_API_DEVICE_STATUS_RF_OFF_SW;
break;
default:
old_status = __old_status;
break;
}
switch (__new_status) {
case WIMAX_API_DEVICE_STATUS_RF_OFF_HW_SW:
case WIMAX_API_DEVICE_STATUS_RF_OFF_HW:
case WIMAX_API_DEVICE_STATUS_RF_OFF_SW:
new_status = WIMAX_API_DEVICE_STATUS_RF_OFF_SW;
break;
default:
new_status = __new_status;
break;
}
/* If no real state change, do nothing */
if (old_status == new_status) {
_DBG_IWMX("no state changed\n");
return;
} else
_DBG_IWMX("state change from %d (%d: %s) to %d (%d: %s)\n",
old_status, __old_status,
iwmx_sdk_dev_status_to_str(__old_status),
new_status, __new_status,
iwmx_sdk_dev_status_to_str(__new_status));
/* Cleanup old state */
switch (old_status) {
case WIMAX_API_DEVICE_STATUS_UnInitialized:
/* This means the plugin is starting but the device is
* in some state already, so we need to update our
* internal knowledge of it. */
if (new_status > WIMAX_API_DEVICE_STATUS_RF_OFF_SW)
iwmx_cm_dev_enabled(wmxsdk);
break;
case WIMAX_API_DEVICE_STATUS_RF_OFF_SW:
/* This means the radio is being turned on, so enable
* the device ( unless going to uninitialized). */
if (new_status != WIMAX_API_DEVICE_STATUS_RF_OFF_SW)
iwmx_cm_dev_enabled(wmxsdk);
break;
case WIMAX_API_DEVICE_STATUS_Ready:
break;
case WIMAX_API_DEVICE_STATUS_Scanning:
break;
case WIMAX_API_DEVICE_STATUS_Connecting:
break;
case WIMAX_API_DEVICE_STATUS_Data_Connected:
iwmx_cm_dev_disconnected(wmxsdk);
break;
default:
connman_error("wmxsdk: unknown old status %d\n", old_status);
return;
};
/* Implement new state */
switch (new_status) {
case WIMAX_API_DEVICE_STATUS_UnInitialized:
break;
case WIMAX_API_DEVICE_STATUS_RF_OFF_SW:
/* This means the radio is being turned off, so
* disable the device unless coming from uninitialized. */
if (old_status != WIMAX_API_DEVICE_STATUS_UnInitialized)
iwmx_cm_dev_disabled(wmxsdk);
break;
case WIMAX_API_DEVICE_STATUS_Ready:
break;
case WIMAX_API_DEVICE_STATUS_Scanning:
break;
case WIMAX_API_DEVICE_STATUS_Connecting:
break;
case WIMAX_API_DEVICE_STATUS_Data_Connected:
iwmx_cm_dev_connected(wmxsdk);
break;
default:
connman_error("wmxsdk: unknown new status %d\n", old_status);
return;
};
wmxsdk->status = __new_status;
}
/*
* Implement a device state transition [locking version]
*
* See __iwmx_cm_state_change()
*/
void iwmx_cm_state_change(struct wmxsdk *wmxsdk,
WIMAX_API_DEVICE_STATUS __new_status)
{
g_mutex_lock(wmxsdk->status_mutex);
__iwmx_cm_state_change(wmxsdk, __new_status);
g_mutex_unlock(wmxsdk->status_mutex);
}
/*
* Read the cached device status
*/
WIMAX_API_DEVICE_STATUS iwmx_cm_status_get(struct wmxsdk *wmxsdk)
{
WIMAX_API_DEVICE_STATUS status;
g_mutex_lock(wmxsdk->status_mutex);
status = wmxsdk->status;
g_mutex_unlock(wmxsdk->status_mutex);
return status;
}
/*
* Called by connman when a device is enabled by the user
*
* We need to turn the radio on; the state change function will poke
* the internals.
*/
static int iwmx_cm_enable(struct connman_device *dev)
{
int result;
struct wmxsdk *wmxsdk = connman_device_get_data(dev);
connman_inet_ifup(connman_device_get_index(dev));
result = iwmx_sdk_rf_state_set(wmxsdk, WIMAX_API_RF_ON);
return result;
}
/*
* Called by connman when a device is disabled by the user
*
* Simple: just make sure the radio is off; the state change function
* will poke the internals.
*/
static int iwmx_cm_disable(struct connman_device *dev)
{
int result;
struct wmxsdk *wmxsdk = connman_device_get_data(dev);
result = iwmx_sdk_rf_state_set(wmxsdk, WIMAX_API_RF_OFF);
connman_inet_ifdown(connman_device_get_index(dev));
return 0;
}
/*
* Probe deferred call from when the mainloop is idle
*
* probe() schedules this to be called from the mainloop when idle to
* do a device status evaluation. Needed because of an internal race
* condition in connman. FIXME: deploy into _probe() when fixed.
*/
static gboolean __iwmx_cm_probe_dpc(gpointer _wmxsdk)
{
int result;
struct wmxsdk *wmxsdk = _wmxsdk;
result = iwmx_sdk_get_device_status(wmxsdk);
if (result < 0)
connman_error("wmxsdk: can't get status: %d\n", result);
else
iwmx_cm_state_change(wmxsdk, result);
return FALSE;
}
/*
* Called by connman when a new device pops in
*
* We allocate our private structure, register with the WiMAX API,
* open their device, subscribe to all the callbacks.
*
* At the end, we launch a deferred call (to work around current
* connman issues that need to be fixed in the future) and update the
* device's status. This allows us to pick up the current status and
* adapt connman's idea of the device to it.
*/
static int iwmx_cm_probe(struct connman_device *dev)
{
int result;
struct wmxsdk *wmxsdk = NULL;
wmxsdk = connman_device_get_data(dev);
if (wmxsdk == NULL)
/* not called from a discovery done by the WiMAX
* Network Service, ignore */
return -ENODEV;
result = iwmx_sdk_setup(wmxsdk);
if (result < 0)
goto error_setup;
/* There is a race condition in the connman core that doesn't
* allow us to call this directly and things to work properly
* FIXME FIXME FIXME: merge _dpc call in here when connman is fixed */
g_idle_add(__iwmx_cm_probe_dpc, wmxsdk);
return 0;
iwmx_sdk_remove(wmxsdk);
error_setup:
return result;
}
/*
* Called when a device is removed from connman
*
* Cleanup all that is done in _probe. Remove callbacks, unregister
* from the WiMAX API.
*/
static void iwmx_cm_remove(struct connman_device *dev)
{
struct wmxsdk *wmxsdk = connman_device_get_data(dev);
iwmx_sdk_remove(wmxsdk);
}
/*
* Called by connman to ask the device to scan for networks
*
* We have set in the WiMAX API the scan result callbacks, so we just
* start a simple scan (not a wide one).
*
* First we obtain the current list of networks and pass it to the
* callback processor. Then we start an scan cycle.
*/
static int iwmx_cm_scan(struct connman_device *dev)
{
struct wmxsdk *wmxsdk = connman_device_get_data(dev);
return iwmx_sdk_scan(wmxsdk);
}
/*
* Driver for a WiMAX API based device.
*/
static struct connman_device_driver iwmx_cm_device_driver = {
.name = "iwmx",
.type = CONNMAN_DEVICE_TYPE_WIMAX,
.probe = iwmx_cm_probe,
.remove = iwmx_cm_remove,
.enable = iwmx_cm_enable,
.disable = iwmx_cm_disable,
.scan = iwmx_cm_scan,
};
static int iwmx_cm_init(void)
{
int result;
result = connman_device_driver_register(&iwmx_cm_device_driver);
if (result < 0)
goto error_driver_register;
result = connman_network_driver_register(&iwmx_cm_network_driver);
if (result < 0)
goto error_network_driver_register;
result = iwmx_sdk_api_init();
if (result < 0)
goto error_iwmx_sdk_init;
return 0;
error_iwmx_sdk_init:
connman_network_driver_unregister(&iwmx_cm_network_driver);
error_network_driver_register:
connman_device_driver_unregister(&iwmx_cm_device_driver);
error_driver_register:
return result;
}
static void iwmx_cm_exit(void)
{
iwmx_sdk_api_exit();
connman_network_driver_unregister(&iwmx_cm_network_driver);
connman_device_driver_unregister(&iwmx_cm_device_driver);
}
CONNMAN_PLUGIN_DEFINE(iwmx, "Intel WiMAX SDK / Common API plugin",
CONNMAN_VERSION, CONNMAN_PLUGIN_PRIORITY_LOW,
iwmx_cm_init, iwmx_cm_exit);