/*
 * ***************************************************************************
 *  FILE:     ul_int.c
 *
 *  PURPOSE:
 *      Manage list of client applications using UniFi.
 *
 * Copyright (C) 2006-2009 by Cambridge Silicon Radio Ltd.
 *
 * Refer to LICENSE.txt included with this source code for details on
 * the license terms.
 *
 * ***************************************************************************
 */
#include "csr_wifi_hip_unifi.h"
#include "csr_wifi_hip_conversions.h"
#include "unifi_priv.h"
#include "unifiio.h"
#include "unifi_os.h"

static void free_bulkdata_buffers(unifi_priv_t *priv, bulk_data_param_t *bulkdata);
static void reset_driver_status(unifi_priv_t *priv);

/*
 * ---------------------------------------------------------------------------
 *  ul_init_clients
 *
 *      Initialise the clients array to empty.
 *
 *  Arguments:
 *      priv            Pointer to device private context struct
 *
 *  Returns:
 *      None.
 *
 *  Notes:
 *      This function needs to be called before priv is stored in
 *      Unifi_instances[].
 * ---------------------------------------------------------------------------
 */
void
ul_init_clients(unifi_priv_t *priv)
{
    int id;
    ul_client_t *ul_clients;

    sema_init(&priv->udi_logging_mutex, 1);
    priv->logging_client = NULL;

    ul_clients = priv->ul_clients;

    for (id = 0; id < MAX_UDI_CLIENTS; id++) {
        memset(&ul_clients[id], 0, sizeof(ul_client_t));

        ul_clients[id].client_id = id;
        ul_clients[id].sender_id = UDI_SENDER_ID_BASE + (id << UDI_SENDER_ID_SHIFT);
        ul_clients[id].instance = -1;
        ul_clients[id].event_hook = NULL;

        INIT_LIST_HEAD(&ul_clients[id].udi_log);
        init_waitqueue_head(&ul_clients[id].udi_wq);
        sema_init(&ul_clients[id].udi_sem, 1);

        ul_clients[id].wake_up_wq_id = 0;
        ul_clients[id].seq_no = 0;
        ul_clients[id].wake_seq_no = 0;
        ul_clients[id].snap_filter.count = 0;
    }
} /* ul_init_clients() */


/*
 * ---------------------------------------------------------------------------
 *  ul_register_client
 *
 *      This function registers a new ul client.
 *
 *  Arguments:
 *      priv            Pointer to device private context struct
 *      configuration   Special configuration for the client.
 *      udi_event_clbk  Callback for receiving event from unifi.
 *
 *  Returns:
 *      0 if a new clients is registered, -1 otherwise.
 * ---------------------------------------------------------------------------
 */
ul_client_t *
ul_register_client(unifi_priv_t *priv, unsigned int configuration,
                   udi_event_t udi_event_clbk)
{
    unsigned char id, ref;
    ul_client_t *ul_clients;

    ul_clients = priv->ul_clients;

    /* check for an unused entry */
    for (id = 0; id < MAX_UDI_CLIENTS; id++) {
        if (ul_clients[id].udi_enabled == 0) {
            ul_clients[id].instance = priv->instance;
            ul_clients[id].udi_enabled = 1;
            ul_clients[id].configuration = configuration;

            /* Allocate memory for the reply signal.. */
            ul_clients[id].reply_signal = kmalloc(sizeof(CSR_SIGNAL), GFP_KERNEL);
            if (ul_clients[id].reply_signal == NULL) {
                unifi_error(priv, "Failed to allocate reply signal for client.\n");
                return NULL;
            }
            /* .. and the bulk data of the reply signal. */
            for (ref = 0; ref < UNIFI_MAX_DATA_REFERENCES; ref ++) {
                ul_clients[id].reply_bulkdata[ref] = kmalloc(sizeof(bulk_data_t), GFP_KERNEL);
                /* If allocation fails, free allocated memory. */
                if (ul_clients[id].reply_bulkdata[ref] == NULL) {
                    for (; ref > 0; ref --) {
                        kfree(ul_clients[id].reply_bulkdata[ref - 1]);
                    }
                    kfree(ul_clients[id].reply_signal);
                    unifi_error(priv, "Failed to allocate bulk data buffers for client.\n");
                    return NULL;
                }
            }

            /* Set the event callback. */
            ul_clients[id].event_hook = udi_event_clbk;

            unifi_trace(priv, UDBG2, "UDI %d (0x%x) registered. configuration = 0x%x\n",
                        id, &ul_clients[id], configuration);
            return &ul_clients[id];
        }
    }
    return NULL;
} /* ul_register_client() */


/*
 * ---------------------------------------------------------------------------
 *  ul_deregister_client
 *
 *      This function deregisters a blocking UDI client.
 *
 *  Arguments:
 *      client      Pointer to the client we deregister.
 *
 *  Returns:
 *      0 if a new clients is deregistered.
 * ---------------------------------------------------------------------------
 */
int
ul_deregister_client(ul_client_t *ul_client)
{
    struct list_head *pos, *n;
    udi_log_t *logptr;
    unifi_priv_t *priv = uf_find_instance(ul_client->instance);
    int ref;

    ul_client->instance = -1;
    ul_client->event_hook = NULL;
    ul_client->udi_enabled = 0;
    unifi_trace(priv, UDBG5, "UDI (0x%x) deregistered.\n", ul_client);

    /* Free memory allocated for the reply signal and its bulk data. */
    kfree(ul_client->reply_signal);
    for (ref = 0; ref < UNIFI_MAX_DATA_REFERENCES; ref ++) {
        kfree(ul_client->reply_bulkdata[ref]);
    }

    if (ul_client->snap_filter.count) {
        ul_client->snap_filter.count = 0;
        kfree(ul_client->snap_filter.protocols);
    }

    /* Free anything pending on the udi_log list */
    down(&ul_client->udi_sem);
    list_for_each_safe(pos, n, &ul_client->udi_log)
    {
        logptr = list_entry(pos, udi_log_t, q);
        list_del(pos);
        kfree(logptr);
    }
    up(&ul_client->udi_sem);

    return 0;
} /* ul_deregister_client() */



/*
 * ---------------------------------------------------------------------------
 *  logging_handler
 *
 *      This function is registered with the driver core.
 *      It is called every time a UniFi HIP Signal is sent. It iterates over
 *      the list of processes interested in receiving log events and
 *      delivers the events to them.
 *
 *  Arguments:
 *      ospriv      Pointer to driver's private data.
 *      sigdata     Pointer to the packed signal buffer.
 *      signal_len  Length of the packed signal.
 *      bulkdata    Pointer to the signal's bulk data.
 *      dir         Direction of the signal
 *                  0 = from-host
 *                  1 = to-host
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
void
logging_handler(void *ospriv,
                u8 *sigdata, u32 signal_len,
                const bulk_data_param_t *bulkdata,
                enum udi_log_direction direction)
{
    unifi_priv_t *priv = (unifi_priv_t*)ospriv;
    ul_client_t *client;
    int dir;

    dir = (direction == UDI_LOG_FROM_HOST) ? UDI_FROM_HOST : UDI_TO_HOST;

    down(&priv->udi_logging_mutex);
    client = priv->logging_client;
    if (client != NULL) {
        client->event_hook(client, sigdata, signal_len,
                           bulkdata, dir);
    }
    up(&priv->udi_logging_mutex);

} /* logging_handler() */



/*
 * ---------------------------------------------------------------------------
 *  ul_log_config_ind
 *
 *      This function uses the client's register callback
 *      to indicate configuration information e.g core errors.
 *
 *  Arguments:
 *      priv        Pointer to driver's private data.
 *      conf_param  Pointer to the configuration data.
 *      len         Length of the configuration data.
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
void
ul_log_config_ind(unifi_priv_t *priv, u8 *conf_param, int len)
{
#ifdef CSR_SUPPORT_SME
    if (priv->smepriv == NULL)
    {
        return;
    }
    if ((CONFIG_IND_ERROR == (*conf_param)) && (priv->wifi_on_state == wifi_on_in_progress)) {
        unifi_notice(priv, "ul_log_config_ind: wifi on in progress, suppress error\n");
    } else {
        /* wifi_off_ind (error or exit) */
        CsrWifiRouterCtrlWifiOffIndSend(priv->CSR_WIFI_SME_IFACEQUEUE,0, (CsrWifiRouterCtrlControlIndication)(*conf_param));
    }
#ifdef CSR_WIFI_HIP_DEBUG_OFFLINE
    unifi_debug_buf_dump();
#endif
#else
    bulk_data_param_t bulkdata;

    /*
     * If someone killed unifi_managed before the driver was unloaded
     * the g_drvpriv pointer is going to be NULL. In this case it is
     * safe to assume that there is no client to get the indication.
     */
    if (!priv) {
        unifi_notice(NULL, "uf_sme_event_ind: NULL priv\n");
        return;
    }

    /* Create a null bulkdata structure. */
    bulkdata.d[0].data_length = 0;
    bulkdata.d[1].data_length = 0;

    sme_native_log_event(priv->sme_cli, conf_param, sizeof(u8),
                         &bulkdata, UDI_CONFIG_IND);

#endif /* CSR_SUPPORT_SME */

} /* ul_log_config_ind */


/*
 * ---------------------------------------------------------------------------
 *  free_bulkdata_buffers
 *
 *      Free the bulkdata buffers e.g. after a failed unifi_send_signal().
 *
 *  Arguments:
 *      priv        Pointer to device private struct
 *      bulkdata    Pointer to bulkdata parameter table
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
static void
free_bulkdata_buffers(unifi_priv_t *priv, bulk_data_param_t *bulkdata)
{
    int i;

    if (bulkdata) {
        for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; ++i) {
            if (bulkdata->d[i].data_length != 0) {
                unifi_net_data_free(priv, (bulk_data_desc_t *)(&bulkdata->d[i]));
                /* data_length is now 0 */
            }
        }
    }

} /* free_bulkdata_buffers */

static int
_align_bulk_data_buffers(unifi_priv_t *priv, u8 *signal,
                         bulk_data_param_t *bulkdata)
{
    unsigned int i;

    if ((bulkdata == NULL) || (CSR_WIFI_ALIGN_BYTES == 0)) {
        return 0;
    }

    for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; i++)
    {
        struct sk_buff *skb;
        /*
        * The following complex casting is in place in order to eliminate 64-bit compilation warning
        * "cast to/from pointer from/to integer of different size"
        */
        u32 align_offset = (u32)(long)(bulkdata->d[i].os_data_ptr) & (CSR_WIFI_ALIGN_BYTES-1);
        if (align_offset)
        {
            skb = (struct sk_buff*)bulkdata->d[i].os_net_buf_ptr;
            if (skb == NULL) {
                unifi_warning(priv,
                              "_align_bulk_data_buffers: Align offset found (%d) but skb is NULL!\n",
                              align_offset);
                return -EINVAL;
            }
            if (bulkdata->d[i].data_length == 0) {
                unifi_warning(priv,
                              "_align_bulk_data_buffers: Align offset found (%d) but length is zero\n",
                              align_offset);
                return CSR_RESULT_SUCCESS;
            }
            unifi_trace(priv, UDBG5,
                        "Align f-h buffer (0x%p) by %d bytes (skb->data: 0x%p)\n",
                        bulkdata->d[i].os_data_ptr, align_offset, skb->data);


            /* Check if there is enough headroom... */
            if (unlikely(skb_headroom(skb) < align_offset))
            {
                struct sk_buff *tmp = skb;

                unifi_trace(priv, UDBG5, "Headroom not enough - realloc it\n");
                skb = skb_realloc_headroom(skb, align_offset);
                if (skb == NULL) {
                    unifi_error(priv,
                                "_align_bulk_data_buffers: skb_realloc_headroom failed - signal is dropped\n");
                    return -EFAULT;
                }
                /* Free the old bulk data only if allocation succeeds */
                kfree_skb(tmp);
                /* Bulkdata needs to point to the new skb */
                bulkdata->d[i].os_net_buf_ptr = (const unsigned char*)skb;
                bulkdata->d[i].os_data_ptr = (const void*)skb->data;
            }
            /* ... before pushing the data to the right alignment offset */
            skb_push(skb, align_offset);

        }
        /* The direction bit is zero for the from-host */
        signal[SIZEOF_SIGNAL_HEADER + (i * SIZEOF_DATAREF) + 1] = align_offset;

    }
    return 0;
} /* _align_bulk_data_buffers() */


/*
 * ---------------------------------------------------------------------------
 *  ul_send_signal_unpacked
 *
 *      This function sends a host formatted signal to unifi.
 *
 *  Arguments:
 *      priv        Pointer to driver's private data.
 *      sigptr      Pointer to the signal.
 *      bulkdata    Pointer to the signal's bulk data.
 *
 *  Returns:
 *      O on success, error code otherwise.
 *
 *  Notes:
 *  The signals have to be sent in the format described in the host interface
 *  specification, i.e wire formatted. Certain clients use the host formatted
 *  structures. The write_pack() transforms the host formatted signal
 *  into the wired formatted signal. The code is in the core, since the signals
 *  are defined therefore binded to the host interface specification.
 * ---------------------------------------------------------------------------
 */
int
ul_send_signal_unpacked(unifi_priv_t *priv, CSR_SIGNAL *sigptr,
                        bulk_data_param_t *bulkdata)
{
    u8 sigbuf[UNIFI_PACKED_SIGBUF_SIZE];
    u16 packed_siglen;
    CsrResult csrResult;
    unsigned long lock_flags;
    int r;


    csrResult = write_pack(sigptr, sigbuf, &packed_siglen);
    if (csrResult != CSR_RESULT_SUCCESS) {
        unifi_error(priv, "Malformed HIP signal in ul_send_signal_unpacked()\n");
        return CsrHipResultToStatus(csrResult);
    }
    r = _align_bulk_data_buffers(priv, sigbuf, (bulk_data_param_t*)bulkdata);
    if (r) {
        return r;
    }

    spin_lock_irqsave(&priv->send_signal_lock, lock_flags);
    csrResult = unifi_send_signal(priv->card, sigbuf, packed_siglen, bulkdata);
    if (csrResult != CSR_RESULT_SUCCESS) {
  /*      free_bulkdata_buffers(priv, (bulk_data_param_t *)bulkdata); */
        spin_unlock_irqrestore(&priv->send_signal_lock, lock_flags);
        return CsrHipResultToStatus(csrResult);
    }
    spin_unlock_irqrestore(&priv->send_signal_lock, lock_flags);

    return 0;
} /* ul_send_signal_unpacked() */


/*
 * ---------------------------------------------------------------------------
 *  reset_driver_status
 *
 *      This function is called from ul_send_signal_raw() when it detects
 *      that the SME has sent a MLME-RESET request.
 *
 *  Arguments:
 *      priv        Pointer to device private struct
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
static void
reset_driver_status(unifi_priv_t *priv)
{
    priv->sta_wmm_capabilities = 0;
#ifdef CSR_NATIVE_LINUX
#ifdef CSR_SUPPORT_WEXT
    priv->wext_conf.flag_associated = 0;
    priv->wext_conf.block_controlled_port = CSR_WIFI_ROUTER_PORT_ACTION_8021X_PORT_OPEN;
    priv->wext_conf.bss_wmm_capabilities = 0;
    priv->wext_conf.disable_join_on_ssid_set = 0;
#endif
#endif
} /* reset_driver_status() */


/*
 * ---------------------------------------------------------------------------
 *  ul_send_signal_raw
 *
 *      This function sends a wire formatted data signal to unifi.
 *
 *  Arguments:
 *      priv        Pointer to driver's private data.
 *      sigptr      Pointer to the signal.
 *      siglen      Length of the signal.
 *      bulkdata    Pointer to the signal's bulk data.
 *
 *  Returns:
 *      O on success, error code otherwise.
 * ---------------------------------------------------------------------------
 */
int
ul_send_signal_raw(unifi_priv_t *priv, unsigned char *sigptr, int siglen,
                   bulk_data_param_t *bulkdata)
{
    CsrResult csrResult;
    unsigned long lock_flags;
    int r;

    /*
     * Make sure that the signal is updated with the bulk data
     * alignment for DMA.
     */
    r = _align_bulk_data_buffers(priv, (u8*)sigptr, bulkdata);
    if (r) {
        return r;
    }

    spin_lock_irqsave(&priv->send_signal_lock, lock_flags);
    csrResult = unifi_send_signal(priv->card, sigptr, siglen, bulkdata);
    if (csrResult != CSR_RESULT_SUCCESS) {
        free_bulkdata_buffers(priv, bulkdata);
        spin_unlock_irqrestore(&priv->send_signal_lock, lock_flags);
        return CsrHipResultToStatus(csrResult);
    }
    spin_unlock_irqrestore(&priv->send_signal_lock, lock_flags);

    /*
     * Since this is use by unicli, if we get an MLME reset request
     * we need to initialize a few status parameters
     * that the driver uses to make decisions.
     */
    if (GET_SIGNAL_ID(sigptr) == CSR_MLME_RESET_REQUEST_ID) {
        reset_driver_status(priv);
    }

    return 0;
} /* ul_send_signal_raw() */