/*
 * ***************************************************************************
 *
 *  FILE:     sme_native.c
 *
 * Copyright (C) 2005-2009 by Cambridge Silicon Radio Ltd.
 *
 * Refer to LICENSE.txt included with this source code for details on
 * the license terms.
 *
 * ***************************************************************************
 */

#include <linux/netdevice.h>
#include "unifi_priv.h"
#include "csr_wifi_hip_unifi.h"
#include "csr_wifi_hip_conversions.h"

static const unsigned char wildcard_address[ETH_ALEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

int
uf_sme_init(unifi_priv_t *priv)
{
    sema_init(&priv->mlme_blocking_mutex, 1);

#ifdef CSR_SUPPORT_WEXT
    {
        int r = uf_init_wext_interface(priv);
        if (r != 0) {
            return r;
        }
    }
#endif

    return 0;
} /* uf_sme_init() */


void
uf_sme_deinit(unifi_priv_t *priv)
{

    /* Free memory allocated for the scan table */
/*    unifi_clear_scan_table(priv); */

    /* Cancel any pending workqueue tasks */
    flush_workqueue(priv->unifi_workqueue);

#ifdef CSR_SUPPORT_WEXT
    uf_deinit_wext_interface(priv);
#endif

} /* uf_sme_deinit() */


int sme_mgt_wifi_on(unifi_priv_t *priv)
{
    int r,i;
    s32 csrResult;

    if (priv == NULL) {
        return -EINVAL;
    }
    /* Initialize the interface mode to None */
    for (i=0; i<CSR_WIFI_NUM_INTERFACES; i++) {
        priv->interfacePriv[i]->interfaceMode = 0;
    }

    /* Set up interface mode so that get_packet_priority() can
     * select the right QOS priority when WMM is enabled.
     */
    priv->interfacePriv[0]->interfaceMode = CSR_WIFI_ROUTER_CTRL_MODE_STA;

    r = uf_request_firmware_files(priv, UNIFI_FW_STA);
    if (r) {
        unifi_error(priv, "sme_mgt_wifi_on: Failed to get f/w\n");
        return r;
    }

    /*
     * The request to initialise UniFi might come while UniFi is running.
     * We need to block all I/O activity until the reset completes, otherwise
     * an SDIO error might occur resulting an indication to the SME which
     * makes it think that the initialisation has failed.
     */
    priv->bh_thread.block_thread = 1;

    /* Power on UniFi */
    CsrSdioClaim(priv->sdio);
    csrResult = CsrSdioPowerOn(priv->sdio);
    CsrSdioRelease(priv->sdio);
    if(csrResult != CSR_RESULT_SUCCESS && csrResult != CSR_SDIO_RESULT_NOT_RESET) {
        return -EIO;
    }

    if (csrResult == CSR_RESULT_SUCCESS) {
        /* Initialise UniFi hardware */
        r = uf_init_hw(priv);
        if (r) {
            return r;
        }
    }

    /* Re-enable the I/O thread */
    priv->bh_thread.block_thread = 0;

    /* Disable deep sleep signalling during the firmware initialisation, to
     * prevent the wakeup mechanism raising the SDIO clock beyond INIT before
     * the first MLME-RESET.ind. It gets re-enabled at the CONNECTED.ind,
     * immediately after the MLME-RESET.ind
     */
    csrResult = unifi_configure_low_power_mode(priv->card,
                                           UNIFI_LOW_POWER_DISABLED,
                                           UNIFI_PERIODIC_WAKE_HOST_DISABLED);
    if (csrResult != CSR_RESULT_SUCCESS) {
        unifi_warning(priv,
                      "sme_mgt_wifi_on: unifi_configure_low_power_mode() returned an error\n");
    }


    /* Start the I/O thread */
    CsrSdioClaim(priv->sdio);
    r = uf_init_bh(priv);
    if (r) {
        CsrSdioPowerOff(priv->sdio);
        CsrSdioRelease(priv->sdio);
        return r;
    }
    CsrSdioRelease(priv->sdio);

    priv->init_progress = UNIFI_INIT_FW_DOWNLOADED;

    return 0;
}

int
sme_sys_suspend(unifi_priv_t *priv)
{
    const int interfaceNum = 0;     /* FIXME */
    CsrResult csrResult;

    /* Abort any pending requests. */
    uf_abort_mlme(priv);

    /* Allow our mlme request to go through. */
    priv->io_aborted = 0;

    /* Send MLME-RESET.req to UniFi. */
    unifi_reset_state(priv, priv->netdev[interfaceNum]->dev_addr, 0);

    /* Stop the network traffic */
    netif_carrier_off(priv->netdev[interfaceNum]);

    /* Put UniFi to deep sleep */
    CsrSdioClaim(priv->sdio);
    csrResult = unifi_force_low_power_mode(priv->card);
    CsrSdioRelease(priv->sdio);

    return 0;
} /* sme_sys_suspend() */


int
sme_sys_resume(unifi_priv_t *priv)
{
#ifdef CSR_SUPPORT_WEXT
    /* Send disconnect event so clients will re-initialise connection. */
    memset(priv->wext_conf.current_ssid, 0, UNIFI_MAX_SSID_LEN);
    memset((void*)priv->wext_conf.current_bssid, 0, ETH_ALEN);
    priv->wext_conf.capability = 0;
    wext_send_disassoc_event(priv);
#endif
    return 0;
} /* sme_sys_resume() */


/*
 * ---------------------------------------------------------------------------
 *  sme_native_log_event
 *
 *      Callback function to be registered as the SME event callback.
 *      Copies the signal content into a new udi_log_t struct and adds
 *      it to the read queue for the SME client.
 *
 *  Arguments:
 *      arg             This is the value given to unifi_add_udi_hook, in
 *                      this case a pointer to the client instance.
 *      signal          Pointer to the received signal.
 *      signal_len      Size of the signal structure in bytes.
 *      bulkdata        Pointers to any associated bulk data.
 *      dir             Direction of the signal. Zero means from host,
 *                      non-zero means to host.
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
void
sme_native_log_event(ul_client_t *pcli,
                     const u8 *sig_packed, int sig_len,
                     const bulk_data_param_t *bulkdata,
                     int dir)
{
    unifi_priv_t *priv;
    udi_log_t *logptr;
    u8 *p;
    int i, r;
    int signal_len;
    int total_len;
    udi_msg_t *msgptr;
    CSR_SIGNAL signal;
    ul_client_t *client = pcli;

    if (client == NULL) {
        unifi_error(NULL, "sme_native_log_event: client has exited\n");
        return;
    }

    priv = uf_find_instance(client->instance);
    if (!priv) {
        unifi_error(priv, "invalid priv\n");
        return;
    }

    /* Just a sanity check */
    if ((sig_packed == NULL) || (sig_len <= 0)) {
        return;
    }

    /* Get the unpacked signal */
    r = read_unpack_signal(sig_packed, &signal);
    if (r == 0) {
        signal_len = SigGetSize(&signal);
    } else {
        u16 receiver_id = CSR_GET_UINT16_FROM_LITTLE_ENDIAN((sig_packed) + sizeof(u16)) & 0xFF00;

        /* The control indications are 1 byte, pass them to client. */
        if (sig_len == 1) {
            unifi_trace(priv, UDBG5,
                        "Control indication (0x%x) for native SME.\n",
                        *sig_packed);

            *(u8*)&signal = *sig_packed;
            signal_len = sig_len;
        } else if (receiver_id == 0) {
            /*
             * Also "unknown" signals with a ReceiverId of 0 are passed to the client
             * without unpacking. (This is a code size optimisation to allow signals
             * that the driver not interested in to be dropped from the unpack code).
             */
            unifi_trace(priv, UDBG5,
                        "Signal 0x%.4X with ReceiverId 0 for native SME.\n",
                        CSR_GET_UINT16_FROM_LITTLE_ENDIAN(sig_packed));

            *(u8*)&signal = *sig_packed;
            signal_len = sig_len;
        } else {
            unifi_error(priv,
                        "sme_native_log_event - Received unknown signal 0x%.4X.\n",
                        CSR_GET_UINT16_FROM_LITTLE_ENDIAN(sig_packed));
            return;
        }
    }

    unifi_trace(priv, UDBG3, "sme_native_log_event: signal 0x%.4X for %d\n",
                signal.SignalPrimitiveHeader.SignalId,
                client->client_id);

    total_len = signal_len;
    /* Calculate the buffer we need to store signal plus bulk data */
    for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; i++) {
        total_len += bulkdata->d[i].data_length;
    }

    /* Allocate log structure plus actual signal. */
    logptr = kmalloc(sizeof(udi_log_t) + total_len, GFP_KERNEL);

    if (logptr == NULL) {
        unifi_error(priv,
                    "Failed to allocate %d bytes for a UDI log record\n",
                    sizeof(udi_log_t) + total_len);
        return;
    }

    /* Fill in udi_log struct */
    INIT_LIST_HEAD(&logptr->q);
    msgptr = &logptr->msg;
    msgptr->length = sizeof(udi_msg_t) + total_len;
    msgptr->timestamp = jiffies_to_msecs(jiffies);
    msgptr->direction = dir;
    msgptr->signal_length = signal_len;

    /* Copy signal and bulk data to the log */
    p = (u8 *)(msgptr + 1);
    memcpy(p, &signal, signal_len);
    p += signal_len;

    /* Append any bulk data */
    for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; i++) {
        int len = bulkdata->d[i].data_length;

        /*
         * Len here might not be the same as the length in the bulk data slot.
         * The slot length will always be even, but len could be odd.
         */
        if (len > 0) {
            if (bulkdata->d[i].os_data_ptr) {
                memcpy(p, bulkdata->d[i].os_data_ptr, len);
            } else {
                memset(p, 0, len);
            }
            p += len;
        }
    }

    /* Add to tail of log queue */
    down(&client->udi_sem);
    list_add_tail(&logptr->q, &client->udi_log);
    up(&client->udi_sem);

    /* Wake any waiting user process */
    wake_up_interruptible(&client->udi_wq);

} /* sme_native_log_event() */


/*
 * ---------------------------------------------------------------------------
 *  unifi_ta_indicate_protocol
 *
 *      Report that a packet of a particular type has been seen
 *
 *  Arguments:
 *      drv_priv        The device context pointer passed to ta_init.
 *      protocol        The protocol type enum value.
 *      direction       Whether the packet was a tx or rx.
 *      src_addr        The source MAC address from the data packet.
 *
 *  Returns:
 *      None.
 *
 *  Notes:
 *      We defer the actual sending to a background workqueue,
 *      see uf_ta_ind_wq().
 * ---------------------------------------------------------------------------
 */
void
unifi_ta_indicate_protocol(void *ospriv,
                           CsrWifiRouterCtrlTrafficPacketType packet_type,
                           CsrWifiRouterCtrlProtocolDirection direction,
                           const CsrWifiMacAddress *src_addr)
{

} /* unifi_ta_indicate_protocol */

/*
 * ---------------------------------------------------------------------------
 * unifi_ta_indicate_sampling
 *
 *      Send the TA sampling information to the SME.
 *
 *  Arguments:
 *      drv_priv        The device context pointer passed to ta_init.
 *      stats           The TA sampling data to send.
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
void
unifi_ta_indicate_sampling(void *ospriv, CsrWifiRouterCtrlTrafficStats *stats)
{

} /* unifi_ta_indicate_sampling() */


void
unifi_ta_indicate_l4stats(void *ospriv,
                            u32 rxTcpThroughput,
                            u32 txTcpThroughput,
                            u32 rxUdpThroughput,
                            u32 txUdpThroughput)
{

} /* unifi_ta_indicate_l4stats() */

/*
 * ---------------------------------------------------------------------------
 * uf_native_process_udi_signal
 *
 *      Process interesting signals from the UDI interface.
 *
 *  Arguments:
 *      pcli            A pointer to the client instance.
 *      signal          Pointer to the received signal.
 *      signal_len      Size of the signal structure in bytes.
 *      bulkdata        Pointers to any associated bulk data.
 *      dir             Direction of the signal. Zero means from host,
 *                      non-zero means to host.
 *
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
void
uf_native_process_udi_signal(ul_client_t *pcli,
                             const u8 *packed_signal, int packed_signal_len,
                             const bulk_data_param_t *bulkdata, int dir)
{

} /* uf_native_process_udi_signal() */


/*
 * ---------------------------------------------------------------------------
 *  sme_native_mlme_event_handler
 *
 *      Callback function to be used as the udi_event_callback when registering
 *      as a client.
 *      This function implements a blocking request-reply interface for WEXT.
 *      To use it, a client specifies this function as the udi_event_callback
 *      to ul_register_client(). The signal dispatcher in
 *      unifi_receive_event() will call this function to deliver a signal.
 *
 *  Arguments:
 *      pcli            Pointer to the client instance.
 *      signal          Pointer to the received signal.
 *      signal_len      Size of the signal structure in bytes.
 *      bulkdata        Pointer to structure containing any associated bulk data.
 *      dir             Direction of the signal. Zero means from host,
 *                      non-zero means to host.
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
void
sme_native_mlme_event_handler(ul_client_t *pcli,
                              const u8 *sig_packed, int sig_len,
                              const bulk_data_param_t *bulkdata,
                              int dir)
{
    CSR_SIGNAL signal;
    int signal_len;
    unifi_priv_t *priv = uf_find_instance(pcli->instance);
    int id, r;

    /* Just a sanity check */
    if ((sig_packed == NULL) || (sig_len <= 0)) {
        return;
    }

    /* Get the unpacked signal */
    r = read_unpack_signal(sig_packed, &signal);
    if (r == 0) {
        signal_len = SigGetSize(&signal);
    } else {
        unifi_error(priv,
                    "sme_native_mlme_event_handler - Received unknown signal 0x%.4X.\n",
                    CSR_GET_UINT16_FROM_LITTLE_ENDIAN(sig_packed));
        return;
    }

    id = signal.SignalPrimitiveHeader.SignalId;
    unifi_trace(priv, UDBG4, "wext - Process signal 0x%.4X\n", id);

    /*
     * Take the appropriate action for the signal.
     */
    switch (id) {
        /*
         * Confirm replies from UniFi.
         * These all have zero or one CSR_DATAREF member. (FIXME: check this is still true for softmac)
         */
        case CSR_MA_PACKET_CONFIRM_ID:
        case CSR_MLME_RESET_CONFIRM_ID:
        case CSR_MLME_GET_CONFIRM_ID:
        case CSR_MLME_SET_CONFIRM_ID:
        case CSR_MLME_GET_NEXT_CONFIRM_ID:
        case CSR_MLME_POWERMGT_CONFIRM_ID:
        case CSR_MLME_SCAN_CONFIRM_ID:
        case CSR_MLME_HL_SYNC_CONFIRM_ID:
        case CSR_MLME_MEASURE_CONFIRM_ID:
        case CSR_MLME_SETKEYS_CONFIRM_ID:
        case CSR_MLME_DELETEKEYS_CONFIRM_ID:
        case CSR_MLME_HL_SYNC_CANCEL_CONFIRM_ID:
        case CSR_MLME_ADD_PERIODIC_CONFIRM_ID:
        case CSR_MLME_DEL_PERIODIC_CONFIRM_ID:
        case CSR_MLME_ADD_AUTONOMOUS_SCAN_CONFIRM_ID:
        case CSR_MLME_DEL_AUTONOMOUS_SCAN_CONFIRM_ID:
        case CSR_MLME_SET_PACKET_FILTER_CONFIRM_ID:
        case CSR_MLME_STOP_MEASURE_CONFIRM_ID:
        case CSR_MLME_PAUSE_AUTONOMOUS_SCAN_CONFIRM_ID:
        case CSR_MLME_ADD_TRIGGERED_GET_CONFIRM_ID:
        case CSR_MLME_DEL_TRIGGERED_GET_CONFIRM_ID:
        case CSR_MLME_ADD_BLACKOUT_CONFIRM_ID:
        case CSR_MLME_DEL_BLACKOUT_CONFIRM_ID:
        case CSR_MLME_ADD_RX_TRIGGER_CONFIRM_ID:
        case CSR_MLME_DEL_RX_TRIGGER_CONFIRM_ID:
        case CSR_MLME_CONNECT_STATUS_CONFIRM_ID:
        case CSR_MLME_MODIFY_BSS_PARAMETER_CONFIRM_ID:
        case CSR_MLME_ADD_TEMPLATE_CONFIRM_ID:
        case CSR_MLME_CONFIG_QUEUE_CONFIRM_ID:
        case CSR_MLME_ADD_TSPEC_CONFIRM_ID:
        case CSR_MLME_DEL_TSPEC_CONFIRM_ID:
        case CSR_MLME_START_AGGREGATION_CONFIRM_ID:
        case CSR_MLME_STOP_AGGREGATION_CONFIRM_ID:
        case CSR_MLME_SM_START_CONFIRM_ID:
        case CSR_MLME_LEAVE_CONFIRM_ID:
        case CSR_MLME_SET_TIM_CONFIRM_ID:
        case CSR_MLME_GET_KEY_SEQUENCE_CONFIRM_ID:
        case CSR_MLME_SET_CHANNEL_CONFIRM_ID:
        case CSR_MLME_ADD_MULTICAST_ADDRESS_CONFIRM_ID:
        case CSR_DEBUG_GENERIC_CONFIRM_ID:
            unifi_mlme_copy_reply_and_wakeup_client(pcli, &signal, signal_len, bulkdata);
            break;

        case CSR_MLME_CONNECTED_INDICATION_ID:
            /* We currently ignore the connected-ind for softmac f/w development */
            unifi_info(priv, "CSR_MLME_CONNECTED_INDICATION_ID ignored\n");
            break;

        default:
            break;
    }

} /* sme_native_mlme_event_handler() */



/*
 * -------------------------------------------------------------------------
 *  unifi_reset_state
 *
 *      Ensure that a MAC address has been set.
 *      Send the MLME-RESET signal.
 *      This must be called at least once before starting to do any
 *      network activities (e.g. scan, join etc).
 *
 * Arguments:
 *      priv            Pointer to device private context struct
 *      macaddr         Pointer to chip MAC address.
 *                      If this is FF:FF:FF:FF:FF:FF it will be replaced
 *                      with the MAC address from the chip.
 *      set_default_mib 1 if the f/w must reset the MIB to the default values
 *                      0 otherwise
 *
 * Returns:
 *      0 on success, an error code otherwise.
 * -------------------------------------------------------------------------
 */
int
unifi_reset_state(unifi_priv_t *priv, unsigned char *macaddr,
                  unsigned char set_default_mib)
{
    int r = 0;

#ifdef CSR_SUPPORT_WEXT
    /* The reset clears any 802.11 association. */
    priv->wext_conf.flag_associated = 0;
#endif

    return r;
} /* unifi_reset_state() */