/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
 *
 *  Copyright (C) 2011 Red Hat, Inc. All rights reserved.
 *  Copyright (C) 2007-2010  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 <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/if.h>

#include <glib.h>

#include <WiMaxType.h>
#include <WiMaxAPI.h>
#include <WiMaxAPIEx.h>

#include "logging/nm-logging.h"
#include "iwmxsdk.h"

static WIMAX_API_DEVICE_ID g_api;
static GStaticMutex add_remove_mutex = G_STATIC_MUTEX_INIT;

/* Misc utilities */
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))

/* Misc values */
enum {
	/*
	 * WARNING!!!!!
	 *
	 * ONLY ONE DEVICE SUPPORTED
	 *
	 * - on removal, there is no way to know which device was
	 *   removed (the removed device is removed from the list and
	 *   the callback doesn't have any more information than the
	 *   index in the list that getlistdevice would return -- racy
	 *   as hell).
	 *
	 * - on insertion, there is not enough information provided.
	 */
	IWMX_SDK_DEV_MAX = 1,
};

/* Yes, this is dirty; see above on IWMX_SDK_DEV_MAX */
static struct wmxsdk *g_iwmx_sdk_devs[IWMX_SDK_DEV_MAX];

static struct wmxsdk *deviceid_to_wmxsdk(WIMAX_API_DEVICE_ID *device_id)
{
	unsigned cnt;
	for (cnt = 0; cnt < IWMX_SDK_DEV_MAX; cnt++) {
		struct wmxsdk *wmxsdk = g_iwmx_sdk_devs[cnt];
		if (wmxsdk &&
		    wmxsdk->device_id.deviceIndex == device_id->deviceIndex)
			return wmxsdk;
	}
	return NULL;
}

static int deviceid_to_index(WIMAX_API_DEVICE_ID *device_id)
{
	unsigned cnt;

	for (cnt = 0; cnt < IWMX_SDK_DEV_MAX; cnt++) {
		struct wmxsdk *wmxsdk = g_iwmx_sdk_devs[cnt];
		if (wmxsdk && wmxsdk->device_id.deviceIndex == device_id->deviceIndex)
			return cnt;
	}
	return -1;
}

struct wmxsdk *iwmx_sdk_get_wmxsdk_for_iface(const char *iface)
{
	unsigned cnt;

	for (cnt = 0; cnt < IWMX_SDK_DEV_MAX; cnt++) {
		struct wmxsdk *wmxsdk = g_iwmx_sdk_devs[cnt];
		if (wmxsdk && !strcmp(wmxsdk->ifname, iface))
			return wmxsdk;
	}
	return NULL;
}

/*
 * FIXME: pulled it it out of some hole
 *
 * the cinr to percentage computation comes from the L3/L4 doc
 *
 * But some other places (L4 code) have a more complex, seemingly
 * logarithmical computation.
 *
 * Oh well...
 *
 */
static int cinr_to_percentage(int cinr)
{
	int strength;
	if (cinr <= -5)
		strength = 0;
	else if (cinr >= 25)
		strength = 100;
	else	/* Calc percentage on the value from -5 to 25 */
		strength = ((100UL * (cinr - -5)) / (25 - -5));
	return strength;
}

/**************************************************************/

typedef struct {
	WimaxNewWmxsdkFunc callback;
	void *user_data;
} NewSdkCallback;

GStaticMutex new_callbacks_mutex = G_STATIC_MUTEX_INIT;
static GSList *new_callbacks = NULL;

void iwmx_sdk_new_callback_register(WimaxNewWmxsdkFunc callback, void *user_data)
{
	NewSdkCallback *cb;

	cb = g_malloc0 (sizeof (NewSdkCallback));
	g_assert (cb);
	cb->callback = callback;
	cb->user_data = user_data;

	g_static_mutex_lock (&new_callbacks_mutex);
	new_callbacks = g_slist_append (new_callbacks, cb);
	g_static_mutex_unlock (&new_callbacks_mutex);
}

void iwmx_sdk_new_callback_unregister(WimaxNewWmxsdkFunc callback, void *user_data)
{
	GSList *iter;
	NewSdkCallback *found = NULL;

	g_static_mutex_lock (&new_callbacks_mutex);
	for (iter = new_callbacks; iter; iter = g_slist_next (iter)) {
		NewSdkCallback *cb = iter->data;

		if (cb->callback == callback && cb->user_data == user_data) {
			found = cb;
			break;
		}
	}

	if (found) {
		new_callbacks = g_slist_remove (new_callbacks, found);
		g_free (found);
	}
	g_static_mutex_unlock (&new_callbacks_mutex);
}

static void iwmx_sdk_call_new_callbacks(struct wmxsdk *wmxsdk)
{
	GSList *iter;

	g_static_mutex_lock (&new_callbacks_mutex);
	for (iter = new_callbacks; iter; iter = g_slist_next (iter)) {
		NewSdkCallback *cb = iter->data;

		cb->callback (wmxsdk, cb->user_data);
	}
	g_static_mutex_unlock (&new_callbacks_mutex);
}

/****************************************************************/

typedef struct {
	struct wmxsdk *wmxsdk;
	WIMAX_API_DEVICE_STATUS new_status;
	WIMAX_API_DEVICE_STATUS old_status;
	WIMAX_API_STATUS_REASON reason;
	WIMAX_API_CONNECTION_PROGRESS_INFO progress;
} StateChangeInfo;

static gboolean
state_change_handler(gpointer user_data)
{
	StateChangeInfo *info = user_data;

	if (info->wmxsdk->state_change_cb) {
		info->wmxsdk->state_change_cb(info->wmxsdk,
		                              info->new_status,
		                              info->old_status,
		                              info->reason,
		                              info->progress,
		                              info->wmxsdk->callback_data);
	}
	wmxsdk_unref(info->wmxsdk);
	memset(info, 0, sizeof(*info));
	free(info);
	return FALSE;
}

static void
_schedule_state_change(struct wmxsdk *wmxsdk,
                       WIMAX_API_DEVICE_STATUS new_status,
                       WIMAX_API_DEVICE_STATUS old_status,
                       WIMAX_API_STATUS_REASON reason,
                       WIMAX_API_CONNECTION_PROGRESS_INFO progress)
{
	StateChangeInfo *info;

	info = malloc(sizeof (*info));
	if (!info)
		return;

	memset(info, 0, sizeof(*info));
	info->wmxsdk = wmxsdk;
	info->new_status = new_status;
	info->old_status = old_status;
	info->reason = reason;
	info->progress = progress;

	wmxsdk_ref(wmxsdk);
	g_idle_add(state_change_handler, info);
}

typedef struct {
	struct wmxsdk *wmxsdk;
	WIMAX_API_MEDIA_STATUS media_status;
} MediaStatusInfo;

static gboolean
media_status_change_handler(gpointer user_data)
{
	MediaStatusInfo *info = user_data;

	if (info->wmxsdk->media_status_cb) {
		info->wmxsdk->media_status_cb(info->wmxsdk,
		                              info->media_status,
		                              info->wmxsdk->callback_data);
	}
	wmxsdk_unref(info->wmxsdk);
	memset(info, 0, sizeof(*info));
	free(info);
	return FALSE;
}

static void
_schedule_media_status_change(struct wmxsdk *wmxsdk,
                              WIMAX_API_MEDIA_STATUS media_status)
{
	MediaStatusInfo *info;

	info = malloc(sizeof (*info));
	if (!info)
		return;

	memset(info, 0, sizeof(*info));
	info->wmxsdk = wmxsdk;
	info->media_status = media_status;

	wmxsdk_ref(wmxsdk);
	g_idle_add(media_status_change_handler, info);
}

typedef struct {
	struct wmxsdk *wmxsdk;
	WIMAX_API_NETWORK_CONNECTION_RESP result;
} ConnectResultInfo;

static gboolean
connect_result_handler(gpointer user_data)
{
	ConnectResultInfo *info = user_data;

	if (info->wmxsdk->connect_result_cb) {
		info->wmxsdk->connect_result_cb(info->wmxsdk,
		                                info->result,
		                                info->wmxsdk->callback_data);
	}
	wmxsdk_unref(info->wmxsdk);
	memset(info, 0, sizeof(*info));
	free(info);
	return FALSE;
}

static void
_schedule_connect_result(struct wmxsdk *wmxsdk,
                         WIMAX_API_NETWORK_CONNECTION_RESP resp)
{
	ConnectResultInfo *info;

	info = malloc(sizeof (*info));
	if (!info)
		return;

	memset(info, 0, sizeof(*info));
	info->wmxsdk = wmxsdk;
	info->result = resp;

	wmxsdk_ref(wmxsdk);
	g_idle_add(connect_result_handler, info);
}

typedef struct {
	struct wmxsdk *wmxsdk;
	WIMAX_API_NSP_INFO_EX *nsps;
	guint num_nsps;
} ScanResultInfo;

static gboolean
scan_result_handler(gpointer user_data)
{
	ScanResultInfo *info = user_data;

	if (info->wmxsdk->scan_result_cb) {
		info->wmxsdk->scan_result_cb(info->wmxsdk,
		                             info->nsps,
		                             info->num_nsps,
		                             info->wmxsdk->callback_data);
	}
	wmxsdk_unref(info->wmxsdk);
	free(info->nsps);
	memset(info, 0, sizeof(*info));
	free(info);
	return FALSE;
}

static void
_schedule_scan_result(struct wmxsdk *wmxsdk,
                      WIMAX_API_NSP_INFO_EX *nsps,
                      guint num_nsps)
{
	ScanResultInfo *info;
	size_t nsps_size;
	int i, tmp;

	info = malloc(sizeof (*info));
	if (!info)
		return;

	memset(info, 0, sizeof(*info));
	info->wmxsdk = wmxsdk;

	nsps_size = num_nsps * sizeof (WIMAX_API_NSP_INFO_EX);
	info->nsps = malloc(nsps_size);
	memcpy(info->nsps, nsps, nsps_size);
	info->num_nsps = num_nsps;

	/* CAPI may report link quality as zero -- if it does check if it is a bug
	 * by computing it based on CINR. If it is different, use the computed one.
	 */
	for (i = 0; i < num_nsps; i++) {
		WIMAX_API_NSP_INFO_EX *nsp = &info->nsps[i];

		if (nsp->linkQuality == 0) {
			tmp = cinr_to_percentage(nsp->CINR - 10);
			if (tmp != nsp->linkQuality)
				nsp->linkQuality = tmp;
		}
	}

	wmxsdk_ref(wmxsdk);
	g_idle_add(scan_result_handler, info);
}

static gboolean
removed_handler(gpointer user_data)
{
	struct wmxsdk *wmxsdk = user_data;

	if (wmxsdk->removed_cb)
		wmxsdk->removed_cb(wmxsdk, wmxsdk->callback_data);
	wmxsdk_unref(wmxsdk);
	return FALSE;
}

static void
_schedule_removed(struct wmxsdk *wmxsdk)
{
	wmxsdk_ref(wmxsdk);
	g_idle_add(removed_handler, wmxsdk);
}

/****************************************************************/

/*
 * Convert a WiMAX API status to an string.
 */
const char *iwmx_sdk_dev_status_to_str(WIMAX_API_DEVICE_STATUS status)
{
	switch (status) {
	case WIMAX_API_DEVICE_STATUS_UnInitialized:
		return "uninitialized";
	case WIMAX_API_DEVICE_STATUS_RF_OFF_HW_SW:
		return "rf off";
	case WIMAX_API_DEVICE_STATUS_RF_OFF_HW:
		return "rf off (hard-block)";
	case WIMAX_API_DEVICE_STATUS_RF_OFF_SW:
		return "rf off (soft-block)";
	case WIMAX_API_DEVICE_STATUS_Ready:
		return "ready";
	case WIMAX_API_DEVICE_STATUS_Scanning:
		return "scanning";
	case WIMAX_API_DEVICE_STATUS_Connecting:
		return "connecting";
	case WIMAX_API_DEVICE_STATUS_Data_Connected:
		return "connected";
	default:
		return "unknown";
	}
}

const char *iwmx_sdk_reason_to_str(WIMAX_API_STATUS_REASON reason)
{
	switch (reason) {
	case WIMAX_API_STATUS_REASON_Normal:
		return "normal";

	/**< Failed to complete NW entry with the selected operator (unspecified reason).  */
	case WIMAX_API_STATUS_REASON_Fail_to_connect_to_NW:
		return "unspecified failure";

	/**< Failed to complete ranging */
	case WIMAX_API_STATUS_REASON_Fail_to_connect_Ranging:
		return "ranging failed";

	/**< SBC phase failed */
	case WIMAX_API_STATUS_REASON_Fail_to_connect_SBC:
		return "sbc failed";

	/**< Security error. EAP authentication failed device level */
	case WIMAX_API_STATUS_REASON_Fail_to_connect_EAP_AUTH_Device:
		return "EAP device auth failed";

	/**< Security error. EAP authentication failed user level */
	case WIMAX_API_STATUS_REASON_Fail_to_connect_EAP_AUTH_user:
		return "EAP user auth failed";

	/**< Security error. Handshake failed */
	case WIMAX_API_STATUS_REASON_Fail_to_connect_3_Way_Handshake:
		return "3 way handshake failed";

	/**< Registration failed */
	case WIMAX_API_STATUS_REASON_Fail_to_connect_REG:
		return "registration failed";

	/**< Failed to initialize the data path (failed to perform DSA to one UL and one DL SFs). */
	case WIMAX_API_STATUS_REASON_Fail_to_connect_datapath:
		return "datapath failed";

	default:
		return "unknown";
	}
}

const char *iwmx_sdk_media_status_to_str(WIMAX_API_MEDIA_STATUS status)
{
	switch (status) {
	case WIMAX_API_MEDIA_STATUS_LINK_UP:
		return "link-up";
	case WIMAX_API_MEDIA_STATUS_LINK_DOWN:
		return "link-down";
	case WIMAX_API_MEDIA_STATUS_LINK_RENEW:
		return "link-renew";
	default:
		return "unknown";
	}
}

const char *
iwmx_sdk_con_progress_to_str(WIMAX_API_CONNECTION_PROGRESS_INFO progress)
{
	switch (progress) {

	/**< Device is in Ranging */
	case WIMAX_API_DEVICE_CONNECTION_PROGRESS_Ranging:
		return "ranging";

	/**< Device is in SBC */
	case WIMAX_API_DEVICE_CONNECTION_PROGRESS_SBC:
		return "sbc";

	/**< Device is in EAP authentication Device */
	case WIMAX_API_DEVICE_CONNECTION_PROGRESS_EAP_authentication_Device:
		return "eap-auth-device";

	/**< Device is in EAP authentication User */
	case WIMAX_API_DEVICE_CONNECTION_PROGRESS_EAP_authentication_User:
		return "eap-auth-user";

	/**< Device is in 3-way-handshake */
	case WIMAX_API_DEVICE_CONNECTION_PROGRESS_3_way_handshake:
		return "3way-handshake";

	/**< Device is in Registration */
	case WIMAX_API_DEVICE_CONNECTION_PROGRESS_Registration:
		return "registration";

	/**< Device is in De-registration */
	case WIMAX_API_DEVICE_CONNECTION_PROGRESS_De_registration:
		return "deregistration";

	/**< Device is registered (operational) */
	case WIMAX_API_DEVICE_CONNECTION_PROGRESS_Registered:
		return "registered";

	default:
		return "unknown";
	}
}

/*
 * Get the device's status from the device
 *
 * Does NOT cache the result
 * Does NOT trigger a state change in NetworkManager
 *
 * Returns < 0 errno code on error, status code if ok.
 */
static WIMAX_API_DEVICE_STATUS iwmx_sdk_get_device_status(struct wmxsdk *wmxsdk)
{
	WIMAX_API_RET r;
	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);

	WIMAX_API_DEVICE_STATUS dev_status;
	WIMAX_API_CONNECTION_PROGRESS_INFO pi;

	r = GetDeviceStatus(&wmxsdk->device_id, &dev_status, &pi);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot read device state: %d (%s)", r, errstr);
		dev_status = -EIO;
	}
	return dev_status;
}

/*
 * Get the device's status from the device but return a string describing it
 *
 * Same conditions as iwmx_sdk_get_device_status().
 */
static const char *iwmx_sdk_get_device_status_str(struct wmxsdk *wmxsdk)
{
	const char *result;
	WIMAX_API_DEVICE_STATUS dev_status;

	dev_status = iwmx_sdk_get_device_status(wmxsdk);
	if ((int) dev_status < 0)
		result = "cannot read device state";
	else
		result = iwmx_sdk_dev_status_to_str(dev_status);
	return result;
}

/*
 * If the device is connected but we don't know about the network,
 * create the knowledge of it.
 *
 * Asks the WiMAX API to report which NSP we are connected to and we
 * create/update a network_el in the device's network list. Then
 * return it.
 *
 * Returns NULL on error.
 *
 */
WIMAX_API_CONNECTED_NSP_INFO_EX *iwmx_sdk_get_connected_network(struct wmxsdk *wmxsdk)
{
	WIMAX_API_CONNECTED_NSP_INFO_EX *nsp_info = NULL;
	WIMAX_API_RET r;
	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);

	nsp_info = malloc(sizeof (*nsp_info));
	if (!nsp_info) {
		nm_log_err(LOGD_WIMAX, "wmxsdk: cannot allocate NSP info");
		return NULL;
	}

	r = GetConnectedNSPEx(&wmxsdk->device_id, nsp_info);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot get connected NSP info: %d (%s)", r, errstr);
		free (nsp_info);
		nsp_info = NULL;
	} else {
		/* Migth be 0 sometimes; fix that up */
		if (nsp_info->linkQuality == 0) {
			int linkq_expected = cinr_to_percentage(nsp_info->CINR - 10);
			if (linkq_expected != nsp_info->linkQuality)
				nsp_info->linkQuality = linkq_expected;
		}
	}

	return nsp_info;
}

/*
 * Asks the WiMAX API to report current link statistics.
 *
 * Returns NULL on error.
 *
 */
WIMAX_API_LINK_STATUS_INFO_EX *iwmx_sdk_get_link_status_info(struct wmxsdk *wmxsdk)
{
	WIMAX_API_LINK_STATUS_INFO_EX *stats = NULL;
	WIMAX_API_RET r;
	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);

	/* Only report if connected */
	if (iwmxsdk_status_get(wmxsdk) < WIMAX_API_DEVICE_STATUS_Connecting) {
		nm_log_err(LOGD_WIMAX, "wmxsdk: cannot get link status info unless connected");
		return NULL;
	}

	stats = malloc(sizeof (*stats));
	if (!stats) {
		nm_log_err(LOGD_WIMAX, "wmxsdk: cannot allocate links status info");
		return NULL;
	}

	r = GetLinkStatusEx(&wmxsdk->device_id, stats);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot get link status info: %d (%s)", r, errstr);
		free (stats);
		stats = NULL;
	}

	return stats;
}

/*
 * Callback for a RF State command
 *
 * Called by the WiMAX API when a command sent to change the RF state
 * is completed. This is just a confirmation of what happened with the
 * command.
 *
 * We don't do anything, as when the device changes state, the state
 * change callback is called and that will fiddle with the NetworkManager
 * internals.
 */
static void __iwmx_sdk_rf_state_cb(WIMAX_API_DEVICE_ID *device_id,
				   WIMAX_API_RF_STATE rf_state)
{
	nm_log_dbg(LOGD_WIMAX, "rf_state changed to %d", rf_state);
}

/*
 * Turn the radio on or off
 *
 * First it checks that we are in the right state before doing
 * anything; there might be no need to do anything.
 *
 * Issue a command to the WiMAX API, wait for a callback confirming it
 * is done. Sometimes the callback is missed -- in that case, do force
 * a state change evaluation.
 *
 * Frustration note:
 *
 *      Geezoos efing Xist, they make difficult even the most simple
 *      of the operations
 *
 *      This thing is definitely a pain. If the radio is ON already
 *      and you switch it on again...well, there is no way to tell
 *      because you don't get a callback saying it basically
 *      suceeded. But on the other hand, if the thing was in a
 *      different state and action needs to be taken, you have to wait
 *      for a callback to confirm it's done. However, there is also an
 *      state change callback, which is almost the same, so now you
 *      have to handle things in two "unrelated" threads of execution.
 *
 *      How the shpx are you expected to tell the difference? Check
 *      status first? On timeout? Nice gap (eighteen wheeler size) for
 *      race conditions.
 */
int iwmx_sdk_rf_state_set(struct wmxsdk *wmxsdk, WIMAX_API_RF_STATE rf_state)
{
	int result;

	WIMAX_API_RET r;
	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);
	WIMAX_API_DEVICE_STATUS dev_status;

	g_assert(rf_state == WIMAX_API_RF_ON || rf_state == WIMAX_API_RF_OFF);

	/* Guess what the current radio state is; if it is ON
	 * already, don't redo it. */
	dev_status = iwmx_sdk_get_device_status(wmxsdk);
	if ((int) dev_status < 0) {
		result = dev_status;
		goto error_get_status;
	}
	switch (dev_status) {
	case WIMAX_API_DEVICE_STATUS_UnInitialized:
		result = -EINVAL;
		goto error_cant_do;
	case WIMAX_API_DEVICE_STATUS_RF_OFF_HW_SW:
	case WIMAX_API_DEVICE_STATUS_RF_OFF_HW:
		nm_log_err(LOGD_WIMAX, "wmxsdk: cannot turn on radio: hw switch is off");
		result = -EPERM;
		goto error_cant_do;
		break;
	case WIMAX_API_DEVICE_STATUS_RF_OFF_SW:
		if (rf_state == WIMAX_API_RF_OFF) {
			result = 0;
			nm_log_dbg(LOGD_WIMAX, "radio is already off");
			goto out_done;
		}
		break;
	case WIMAX_API_DEVICE_STATUS_Ready:
	case WIMAX_API_DEVICE_STATUS_Scanning:
	case WIMAX_API_DEVICE_STATUS_Connecting:
	case WIMAX_API_DEVICE_STATUS_Data_Connected:
		if (rf_state == WIMAX_API_RF_ON) {
			result = 0;
			nm_log_dbg(LOGD_WIMAX, "radio is already on");
			goto out_done;
		}
		break;
	default:
		g_assert(1);
	}
	/* Ok, flip the radio */
	r = CmdControlPowerManagement(&wmxsdk->device_id, rf_state);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot flip radio to %d: %d (%s) [device is in state %s]",
		           rf_state, r, errstr, iwmx_sdk_get_device_status_str(wmxsdk));
		result = -EIO;
	} else
		result = -EINPROGRESS;
out_done:
error_cant_do:
error_get_status:
	return result;
}

/*
 * Read the cached device status
 */
WIMAX_API_DEVICE_STATUS iwmxsdk_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;
}

/*
 * Callback for a Connect command
 *
 * Called by the WiMAX API when a command sent to connect is
 * completed. This is just a confirmation of what happened with the
 * command.
 *
 * WE DON'T DO MUCH HERE -- the real meat happens when a state change
 * callback is sent, where we detect we move to connected state (or
 * from disconnecting to something else); the state change callback is
 * called and that will fiddle with the NetworkManager internals.
 */
static void __iwmx_sdk_connect_cb(WIMAX_API_DEVICE_ID *device_id,
				  WIMAX_API_NETWORK_CONNECTION_RESP resp)
{
	WIMAX_API_DEVICE_STATUS status;
	struct wmxsdk *wmxsdk = deviceid_to_wmxsdk(device_id);

	status = iwmxsdk_status_get(wmxsdk);
	if (resp == WIMAX_API_CONNECTION_SUCCESS) {
		if (status != WIMAX_API_DEVICE_STATUS_Data_Connected) {
			nm_log_err(LOGD_WIMAX, "wmxsdk: error: connect worked, but state"
			           " didn't change (now it is %d [%s])",
			           status,
			           iwmx_sdk_dev_status_to_str(status));
		}
	} else {
		nm_log_err(LOGD_WIMAX, "wmxsdk: failed to connect (status %d: %s)",
		           status, iwmx_sdk_dev_status_to_str(status));
	}

	_schedule_connect_result(wmxsdk, resp);
}

/*
 * Connect to a network
 *
 * This function starts the connection process to a given network;
 * when the device changes status, the status change callback will
 * tell NetworkManager if the network is finally connected or not.
 *
 * One of the reasons it is done like that is to allow external tools
 * to control the device and the plugin just passing the status so
 * NetworkManager displays the right info.
 */
int iwmx_sdk_connect(struct wmxsdk *wmxsdk, const char *nsp_name)
{
	int result = 0;

	WIMAX_API_RET r;
	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);
	WIMAX_API_DEVICE_STATUS dev_status;
	char sdk_name[MAX_SIZE_OF_NSP_NAME];

	g_mutex_lock(wmxsdk->connect_mutex);
	/* Guess what the current radio state is; if it is ON
	 * already, don't redo it. */
	dev_status = iwmxsdk_status_get(wmxsdk);
	if ((int) dev_status < 0) {
		result = dev_status;
		goto error_get_status;
	}
	switch (dev_status) {
	case WIMAX_API_DEVICE_STATUS_UnInitialized:
		nm_log_err(LOGD_WIMAX, "wmxsdk: SW BUG? HW is uninitialized");
		result = -EINVAL;
		goto error_cant_do;
	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:
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot connect: radio is off");
		result = -EPERM;
		goto error_cant_do;
	case WIMAX_API_DEVICE_STATUS_Ready:
	case WIMAX_API_DEVICE_STATUS_Scanning:
		break;
	case WIMAX_API_DEVICE_STATUS_Connecting:
		nm_log_dbg(LOGD_WIMAX, "Connect already pending, waiting for it");
		result = -EINPROGRESS;
		goto error_cant_do;
	case WIMAX_API_DEVICE_STATUS_Data_Connected:
		nm_log_err(LOGD_WIMAX, "wmxsdk: BUG? need to disconnect?");
		result = -EINVAL;
		goto error_cant_do;
	default:
		g_assert(1);
	}

	/* The SDK treats the network name as wchar_t* while the contents are
	 * actually just UTF-8...  WTF?  Hand it a full buffer to work around
	 * boundary cases where the NSP name contains an odd # of characters.
	 */
	memset(sdk_name, 0, sizeof (sdk_name));
	memcpy(sdk_name, nsp_name, strlen (nsp_name));

	/* Ok, do the connection, wait for a callback */
	r = CmdConnectToNetwork(&wmxsdk->device_id, &sdk_name[0], 0, 0);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot connect to network %s: %d (%s) - device is in state '%s'",
		           nsp_name, r, errstr,
		           iwmx_sdk_get_device_status_str(wmxsdk));
		result = -EIO;
	}

error_cant_do:
error_get_status:
	g_mutex_unlock(wmxsdk->connect_mutex);
	return result;
}

/*
 * Callback for a Disconnect command
 *
 * Called by the WiMAX API when a command sent to connect is
 * completed. This is just a confirmation of what happened with the
 * command.
 *
 * When the device changes state, the state change callback is called
 * and that will fiddle with the NetworkManager internals.
 *
 * We just update the result of the command and wake up anybody who is
 * waiting for this conditional variable.
 */
static void __iwmx_sdk_disconnect_cb(WIMAX_API_DEVICE_ID *device_id,
				     WIMAX_API_NETWORK_CONNECTION_RESP resp)
{
	struct wmxsdk *wmxsdk = deviceid_to_wmxsdk(device_id);
	WIMAX_API_DEVICE_STATUS status;

	status = iwmxsdk_status_get(wmxsdk);
	if (resp == WIMAX_API_CONNECTION_SUCCESS) {
		if (status == WIMAX_API_DEVICE_STATUS_Data_Connected) {
			nm_log_err(LOGD_WIMAX, "wmxsdk: error: disconnect worked, "
				      "but state didn't change (now it is %d [%s])", status,
				      iwmx_sdk_dev_status_to_str(status));
		}
	} else
		nm_log_err(LOGD_WIMAX, "wmxsdk: failed to disconnect (status %d: %s)",
			      status, iwmx_sdk_dev_status_to_str(status));
}

/*
 * Disconnect from a network
 *
 * This function tells the device to disconnect; the state change
 * callback will take care of inform NetworkManager's internals.
 */
int iwmx_sdk_disconnect(struct wmxsdk *wmxsdk)
{
	int result;

	WIMAX_API_RET r;
	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);
	WIMAX_API_DEVICE_STATUS dev_status;

	g_mutex_lock(wmxsdk->connect_mutex);
	/* Guess what the current radio state is; if it is ON
	 * already, don't redo it. */
	dev_status = iwmx_sdk_get_device_status(wmxsdk);
	if ((int) dev_status < 0) {
		result = dev_status;
		goto error_get_status;
	}
	switch (dev_status) {
	case WIMAX_API_DEVICE_STATUS_UnInitialized:
		nm_log_err(LOGD_WIMAX, "wmxsdk: SW BUG? HW is uninitialized");
		result = -EINVAL;
		goto error_cant_do;
	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:
		nm_log_dbg(LOGD_WIMAX, "Cannot disconnect, radio is off; ignoring");
		result = 0;
		goto error_cant_do;
	case WIMAX_API_DEVICE_STATUS_Ready:
	case WIMAX_API_DEVICE_STATUS_Scanning:
		nm_log_dbg(LOGD_WIMAX, "Cannot disconnect, already disconnected; ignoring");
		result = 0;
		goto error_cant_do;
	case WIMAX_API_DEVICE_STATUS_Connecting:
	case WIMAX_API_DEVICE_STATUS_Data_Connected:
		break;
	default:
		g_assert(1);
	}
	/* Ok, flip the radio */
	r = CmdDisconnectFromNetwork(&wmxsdk->device_id);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot disconnect from network: %d (%s)", r, errstr);
		result = -EIO;
	} else
		result = -EINPROGRESS;
error_cant_do:
error_get_status:
	g_mutex_unlock(wmxsdk->connect_mutex);
	return result;
}

/*
 * Turn fast reconnect capability on/off
 *
 * This function tells wimaxd to turn fast reconnect on or off.
 */
int iwmx_sdk_set_fast_reconnect_enabled(struct wmxsdk *wmxsdk, int enabled)
{
	WIMAX_API_RET r;
	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);

	r = SetFastReconnectCapabilityStatus(&wmxsdk->device_id, !!enabled);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot set fast reconnect to %d: %d (%s)",
		           enabled, r, errstr);
		return -EIO;
	}
	return 0;
}

static void __iwmx_sdk_media_status_update_cb (WIMAX_API_DEVICE_ID_P device_id,
					WIMAX_API_MEDIA_STATUS mediaStatus)
{
	struct wmxsdk *wmxsdk = deviceid_to_wmxsdk(device_id);

	/* Ignore redundant LINK_UP events */
	if (   mediaStatus == WIMAX_API_MEDIA_STATUS_LINK_UP
	    && wmxsdk->media_status == WIMAX_API_MEDIA_STATUS_LINK_UP)
	    return;

	wmxsdk->media_status = mediaStatus;

	nm_log_dbg(LOGD_WIMAX, "wmxsdk: media status change to (%d) %s",
	           mediaStatus, iwmx_sdk_media_status_to_str (mediaStatus));

	_schedule_media_status_change(wmxsdk, mediaStatus);
}

/*
 * Callback for state change messages
 *
 * Just pass them to the state transition handler
 */
static void __iwmx_sdk_state_change_cb(WIMAX_API_DEVICE_ID *device_id,
					WIMAX_API_DEVICE_STATUS status,
					WIMAX_API_STATUS_REASON reason,
					WIMAX_API_CONNECTION_PROGRESS_INFO pi)
{
	struct wmxsdk *wmxsdk = deviceid_to_wmxsdk(device_id);
	WIMAX_API_DEVICE_STATUS old_status;

	nm_log_dbg(LOGD_WIMAX, "wmxsdk: state change to (%d) %s reason (%d) %s",
	           status, iwmx_sdk_dev_status_to_str (status),
	           reason, iwmx_sdk_reason_to_str (reason));

	g_mutex_lock(wmxsdk->status_mutex);
	old_status = wmxsdk->status;
	wmxsdk->status = status;
	g_mutex_unlock(wmxsdk->status_mutex);

	_schedule_state_change(wmxsdk, status, old_status, reason, pi);
}

/*
 * Called by _iwmx_sdk_*scan_cb() when [wide or preferred] scan results
 * are available.
 *
 * From here we update NetworkManager's idea of which networks are available.
 */
static void __iwmx_sdk_scan_common_cb(WIMAX_API_DEVICE_ID *device_id,
				      WIMAX_API_NSP_INFO_EX *nsp_list,
				      UINT32 nsp_list_size)
{
	struct wmxsdk *wmxsdk = deviceid_to_wmxsdk(device_id);

	g_static_mutex_lock(&wmxsdk->network_mutex);
	_schedule_scan_result(wmxsdk, nsp_list, nsp_list_size);
	g_static_mutex_unlock(&wmxsdk->network_mutex);
}

/*
 * Called by the WiMAX API when we get a wide scan result
 *
 * We treat them same as wide, so we just call that.
 */
static void __iwmx_sdk_wide_scan_cb(WIMAX_API_DEVICE_ID *device_id,
				    WIMAX_API_NSP_INFO_EX *nsp_list,
				    UINT32 nsp_list_size)
{
	__iwmx_sdk_scan_common_cb(device_id, nsp_list, nsp_list_size);
}

/*
 * Called by the WiMAX API when we get a normal (non wide) scan result
 *
 * We treat them same as wide, so we just call that.
 */
static void __iwmx_sdk_scan_cb(WIMAX_API_DEVICE_ID *device_id,
				WIMAX_API_NSP_INFO_EX *nsp_list,
				UINT32 nsp_list_size, UINT32 searchProgress)
{
	__iwmx_sdk_scan_common_cb(device_id, nsp_list, nsp_list_size);
}

/*
 * Called to ask the device to scan for networks
 *
 * We don't really scan as the WiMAX SDK daemon scans in the
 * background for us. We just get the results and hand them back via
 * the scan_result_cb callback.
 */
int iwmx_sdk_get_networks(struct wmxsdk *wmxsdk)
{
	int result;

	UINT32 nsp_list_length = 10;
	WIMAX_API_NSP_INFO_EX nsp_list[10];	/* FIXME: up to 32? */

	WIMAX_API_RET r;
	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);

	r = GetNetworkListEx(&wmxsdk->device_id, nsp_list, &nsp_list_length);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot get network list: %d (%s)", r, errstr);
		result = -EIO;
		goto error_scan;
	}

	if (nsp_list_length == 0) {
		nm_log_dbg(LOGD_WIMAX, "no networks");
	} else
		__iwmx_sdk_scan_common_cb(&wmxsdk->device_id, nsp_list,
					nsp_list_length);
	result = 0;
error_scan:
	return result;
}

/*
 * Initialize the WiMAX API, register with it, setup callbacks
 *
 */
static int iwmx_sdk_setup(struct wmxsdk *wmxsdk)
{
	int result, status;

	WIMAX_API_RET r;

	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);

	result = -ENFILE;

	/* device_id initialized by iwmx_sdk_dev_add */

	r = WiMaxDeviceOpen(&wmxsdk->device_id);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot open device: %d (%s)", r, errstr);
		goto error_wimaxdeviceopen;
	}

	/*
	 * We scan in auto mode (in the background)
	 *
	 * Otherwise is messy -- if we have NetworkManager triggering a scan
	 * when we call iwmx_nm_scan() -> iwmx_sdk_scan(), most of the
	 * times that causes a race condition when the UI asks for a
	 * scan right before displaying the network menu. As there is
	 * no way to cancel an ongoing scan before connecting, we are
	 * stuck. So we do auto bg and have iwmx_sdk_scan() just return
	 * the current network list.
	 */
	r = SetConnectionMode(&wmxsdk->device_id,
			      WIMAX_API_CONNECTION_AUTO_SCAN_MANUAL_CONNECT);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot set connectin mode to manual: %d (%s)", r, errstr);
		goto error_connection_mode;
	}

	r = SubscribeControlPowerManagement(&wmxsdk->device_id,
					    __iwmx_sdk_rf_state_cb);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to radio change events: %u (%s)", r, errstr);
		result = -EIO;
		goto error_subscribe_rf_state;
	}

	r = SubscribeDeviceStatusChange(&wmxsdk->device_id,
					__iwmx_sdk_state_change_cb);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to state chaneg events: %d (%s)", r, errstr);
		goto error_subscribe_state_change;
	}

	r = SubscribeNetworkSearchWideScanEx(&wmxsdk->device_id,
					     __iwmx_sdk_wide_scan_cb);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to wide scan events: %d (%s)", r, errstr);
		goto error_subscribe_wide_scan;
	}
	r = SubscribeNetworkSearchEx(&wmxsdk->device_id, __iwmx_sdk_scan_cb);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to scan events: %d (%s)", r, errstr);
		goto error_subscribe_scan;
	}

	r = SubscribeConnectToNetwork(&wmxsdk->device_id,
				      __iwmx_sdk_connect_cb);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to connect events: %d (%s)", r, errstr);
		goto error_subscribe_connect;
	}

	r = SubscribeDisconnectToNetwork(&wmxsdk->device_id,
					 __iwmx_sdk_disconnect_cb);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to disconnect events: %d (%s)", r, errstr);
		goto error_subscribe_disconnect;
	}

	r = SubscribeMediaStatusUpdate(&wmxsdk->device_id, __iwmx_sdk_media_status_update_cb);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to media status events: %d (%s)", r, errstr);
		goto error_subscribe_media_status;
	}

	status = iwmx_sdk_get_device_status(wmxsdk);
	if ((int) status < 0)
		status = WIMAX_API_DEVICE_STATUS_UnInitialized;

	g_mutex_lock(wmxsdk->status_mutex);
	wmxsdk->status = status;
	g_mutex_unlock(wmxsdk->status_mutex);

	_schedule_state_change(wmxsdk,
	                       status,
	                       WIMAX_API_DEVICE_STATUS_UnInitialized,
	                       WIMAX_API_STATUS_REASON_Normal,
	                       WIMAX_API_DEVICE_CONNECTION_PROGRESS_Ranging);

	return 0;

	UnsubscribeMediaStatusUpdate(&wmxsdk->device_id);
error_subscribe_media_status:
	UnsubscribeDisconnectToNetwork(&wmxsdk->device_id);
error_subscribe_disconnect:
	UnsubscribeConnectToNetwork(&wmxsdk->device_id);
error_subscribe_connect:
	UnsubscribeNetworkSearchEx(&wmxsdk->device_id);
error_subscribe_scan:
	UnsubscribeNetworkSearchWideScanEx(&wmxsdk->device_id);
error_subscribe_wide_scan:
	UnsubscribeDeviceStatusChange(&wmxsdk->device_id);
error_subscribe_state_change:
	UnsubscribeControlPowerManagement(&wmxsdk->device_id);
error_subscribe_rf_state:
error_connection_mode:
	WiMaxDeviceClose(&wmxsdk->device_id);
error_wimaxdeviceopen:
	return result;
}

/*
 * Called when a device is torn down
 *
 * Cleanup all that is done in iwmx_sdk_setup(). Remove callbacks,
 * unregister from the WiMAX API.
 */
static void iwmx_sdk_remove(struct wmxsdk *wmxsdk)
{
	UnsubscribeMediaStatusUpdate(&wmxsdk->device_id);
	UnsubscribeDisconnectToNetwork(&wmxsdk->device_id);
	UnsubscribeConnectToNetwork(&wmxsdk->device_id);
	UnsubscribeNetworkSearchEx(&wmxsdk->device_id);
	UnsubscribeNetworkSearchWideScanEx(&wmxsdk->device_id);
	UnsubscribeDeviceStatusChange(&wmxsdk->device_id);
	UnsubscribeControlPowerManagement(&wmxsdk->device_id);
	WiMaxDeviceClose(&wmxsdk->device_id);
}

void iwmx_sdk_set_callbacks(struct wmxsdk *wmxsdk,
                            WimaxStateChangeFunc state_change_cb,
                            WimaxMediaStatusFunc media_status_cb,
                            WimaxConnectResultFunc connect_result_cb,
                            WimaxScanResultFunc scan_result_cb,
                            WimaxRemovedFunc removed_cb,
                            void *user_data)
{
	wmxsdk->state_change_cb = state_change_cb;
	wmxsdk->media_status_cb = media_status_cb;
	wmxsdk->connect_result_cb = connect_result_cb;
	wmxsdk->scan_result_cb = scan_result_cb;
	wmxsdk->removed_cb = removed_cb;
	wmxsdk->callback_data = user_data;
}

/* Initialize a [zeroed] struct wmxsdk */
static struct wmxsdk *wmxsdk_new(void)
{
	struct wmxsdk *wmxsdk;

	wmxsdk = malloc(sizeof(*wmxsdk));
	if (wmxsdk) {
		memset(wmxsdk, 0, sizeof(*wmxsdk));

		wmxsdk->refcount = 1;
		g_static_mutex_init(&wmxsdk->network_mutex);

		wmxsdk->status = WIMAX_API_DEVICE_STATUS_UnInitialized;
		wmxsdk->status_mutex = g_mutex_new();
		g_assert(wmxsdk->status_mutex);

		wmxsdk->connect_mutex = g_mutex_new();
		g_assert(wmxsdk->connect_mutex);
	}
	return wmxsdk;
}

struct wmxsdk *wmxsdk_ref(struct wmxsdk *wmxsdk)
{
	g_atomic_int_add(&wmxsdk->refcount, 1);
	return wmxsdk;
}

void wmxsdk_unref(struct wmxsdk *wmxsdk)
{
	if (g_atomic_int_dec_and_test(&wmxsdk->refcount)) {
		g_mutex_free(wmxsdk->status_mutex);
		g_mutex_free(wmxsdk->connect_mutex);
		memset(wmxsdk, 0, sizeof(*wmxsdk));
		free(wmxsdk);
	}
}

static void iwmx_sdk_dev_add(unsigned idx, unsigned api_idx, const char *name)
{
	struct wmxsdk *wmxsdk;
	const char *s;

	if (idx >= IWMX_SDK_DEV_MAX) {
		nm_log_err(LOGD_WIMAX, "BUG! idx (%u) >= IWMX_SDK_DEV_MAX (%u)", idx, IWMX_SDK_DEV_MAX);
		return;
	}
	if (g_iwmx_sdk_devs[idx] != NULL) {
		nm_log_err(LOGD_WIMAX, "BUG! device index %u already enumerated?", idx);
		return;
	}

	wmxsdk = wmxsdk_new();
	if (wmxsdk == NULL) {
		nm_log_err(LOGD_WIMAX, "Can't allocate %zu bytes", sizeof(*wmxsdk));
		return;
	}

	/*
	 * This depends on a hack in the WiMAX Network Service; it has
	 * to return, as part of the device name, a string "if:IFNAME"
	 * where the OS's device name is stored.
	 */
	s = strstr(name, "if:");
	if (s == NULL
	    || sscanf(s, "if:%15[^ \f\n\r\t\v]", wmxsdk->ifname) != 1) {
		nm_log_err(LOGD_WIMAX, "Cannot extract network interface name off '%s'",
			      name);
		goto error;
	}
	nm_log_dbg(LOGD_WIMAX, "network interface name: '%s'", wmxsdk->ifname);

	strncpy(wmxsdk->name, name, sizeof(wmxsdk->name));
	wmxsdk->device_id.privilege = WIMAX_API_PRIVILEGE_READ_WRITE;
	wmxsdk->device_id.deviceIndex = api_idx;

	if (iwmx_sdk_setup(wmxsdk) != 0) {
		nm_log_err(LOGD_WIMAX, "wxmsdk: %s: cannot set up interface", wmxsdk->ifname);
		goto error;
	}

	g_iwmx_sdk_devs[idx] = wmxsdk;

	/* Notify listeners of new devices */
	iwmx_sdk_call_new_callbacks (wmxsdk);
	return;

error:
	wmxsdk_unref(wmxsdk);
	return;
}

static void iwmx_sdk_dev_rm(unsigned idx)
{
	struct wmxsdk *wmxsdk;

	if (idx >= IWMX_SDK_DEV_MAX) {
		nm_log_err(LOGD_WIMAX, "BUG! idx (%u) >= IWMX_SDK_DEV_MAX (%u)", idx, IWMX_SDK_DEV_MAX);
		return;
	}

	wmxsdk = g_iwmx_sdk_devs[idx];
	_schedule_removed(wmxsdk);
	iwmx_sdk_remove(wmxsdk);
	wmxsdk_unref(wmxsdk);
	g_iwmx_sdk_devs[idx] = NULL;
}

static void iwmx_sdk_addremove_cb(WIMAX_API_DEVICE_ID *devid,
				  BOOL presence)
{
	unsigned int cnt;
	WIMAX_API_RET r;
	WIMAX_API_HW_DEVICE_ID device_id_list[5];
	UINT32 device_id_list_size = ARRAY_SIZE(device_id_list);
	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);

	g_static_mutex_lock(&add_remove_mutex);

	nm_log_dbg(LOGD_WIMAX, "cb: handle %u index #%u is %d", devid->sdkHandle,
	           devid->deviceIndex, presence);

	r = GetListDevice(devid, device_id_list, &device_id_list_size);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(devid, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot obtain list of devices: %d (%s)", r, errstr);
		goto out;
	}

	if (device_id_list_size == 0) {
		nm_log_dbg(LOGD_WIMAX, "No WiMAX devices reported");
	} else {
		for (cnt = 0; cnt < device_id_list_size; cnt++) {
			WIMAX_API_HW_DEVICE_ID *dev =
				device_id_list + cnt;
			nm_log_dbg(LOGD_WIMAX, "#%u index #%u device %s", cnt,
			           dev->deviceIndex, dev->deviceName);
		}
	}

	if (presence) {
		WIMAX_API_HW_DEVICE_ID *dev;

		/* Make sure the wimax NS isn't lying to us */
		if (device_id_list_size < devid->deviceIndex) {
			nm_log_err(LOGD_WIMAX, "wmxsdk: changed device (%u) not in the list? (%u items)",
			           devid->deviceIndex, device_id_list_size);
			goto out;
		}

		/* Add the device to our internal list */
		dev = device_id_list + devid->deviceIndex;
		iwmx_sdk_dev_add(devid->deviceIndex, dev->deviceIndex, dev->deviceName);
	} else {
		/* Remove the device from our internal list */
		cnt = deviceid_to_index(devid);
		if (cnt >= 0)
			iwmx_sdk_dev_rm(cnt);
	}

out:
	g_static_mutex_unlock(&add_remove_mutex);
}

/*
 * Initialize the WiMAX API, register with it, setup callbacks for
 * device coming up / dissapearing
 */
int iwmx_sdk_api_init(void)
{
	int result;
	unsigned int cnt;
	WIMAX_API_RET r;
	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);

	WIMAX_API_HW_DEVICE_ID device_id_list[5];
	UINT32 device_id_list_size = ARRAY_SIZE(device_id_list);

	memset(&g_api, 0, sizeof(g_api));
	g_api.privilege = WIMAX_API_PRIVILEGE_READ_WRITE;

	result = -EIO;
	r = WiMaxAPIOpen(&g_api);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&g_api, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: WiMaxAPIOpen failed with %d (%s)", r, errstr);
		goto error_wimaxapiopen;
	}

	r = SubscribeDeviceInsertRemove(&g_api, iwmx_sdk_addremove_cb);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&g_api, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: insert/remove subscribe failed with %d (%s)", r, errstr);
		goto error_close;
	}

	r = GetListDevice(&g_api, device_id_list, &device_id_list_size);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&g_api, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot obtain list of devices: %d (%s)", r, errstr);
		goto error_close;
	}
	if (device_id_list_size < g_api.deviceIndex) {
		nm_log_err(LOGD_WIMAX, "wmxsdk: changed device (%u) not in the list? (%u items)",
		           g_api.deviceIndex, device_id_list_size);
	}

	if (device_id_list_size == 0) {
		nm_log_dbg(LOGD_WIMAX, "No WiMAX devices reported");
	} else {
		for (cnt = 0; cnt < device_id_list_size; cnt++) {
			WIMAX_API_HW_DEVICE_ID *dev = device_id_list + cnt;
			nm_log_dbg(LOGD_WIMAX, "#%u index #%u device %s", cnt, dev->deviceIndex, dev->deviceName);
			iwmx_sdk_dev_add(cnt, dev->deviceIndex, dev->deviceName);
		}
	}
	return 0;

error_close:
	WiMaxAPIClose(&g_api);
error_wimaxapiopen:
	return result;
}

void iwmx_sdk_api_exit(void)
{
	WIMAX_API_RET r;

	char errstr[512];
	UINT32 errstr_size = sizeof(errstr);

	r = WiMaxAPIClose(&g_api);
	if (r != WIMAX_API_RET_SUCCESS) {
		GetErrorString(&g_api, r, errstr, &errstr_size);
		nm_log_err(LOGD_WIMAX, "wmxsdk: WiMaxAPIClose failed with %d (%s)", r, errstr);
	}
	return;
}
