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

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



    int
convert_sme_error(CsrResult error)
{
    switch (error) {
        case CSR_RESULT_SUCCESS:
            return 0;
        case CSR_RESULT_FAILURE:
        case CSR_WIFI_RESULT_NOT_FOUND:
        case CSR_WIFI_RESULT_TIMED_OUT:
        case CSR_WIFI_RESULT_CANCELLED:
        case CSR_WIFI_RESULT_UNAVAILABLE:
            return -EIO;
        case CSR_WIFI_RESULT_NO_ROOM:
            return -EBUSY;
        case CSR_WIFI_RESULT_INVALID_PARAMETER:
            return -EINVAL;
        case CSR_WIFI_RESULT_UNSUPPORTED:
            return -EOPNOTSUPP;
        default:
            return -EIO;
    }
}


/*
 * ---------------------------------------------------------------------------
 *  sme_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_log_event(ul_client_t *pcli,
        const u8 *signal, int signal_len,
        const bulk_data_param_t *bulkdata,
        int dir)
{
    unifi_priv_t *priv;
    CSR_SIGNAL unpacked_signal;
    CsrWifiSmeDataBlock mlmeCommand;
    CsrWifiSmeDataBlock dataref1;
    CsrWifiSmeDataBlock dataref2;
    CsrResult result = CSR_RESULT_SUCCESS;
    int r;

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

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

    if (priv->smepriv == NULL) {
        unifi_error(priv, "sme_log_event: invalid smepriv\n");
        return;
    }

    unifi_trace(priv, UDBG3,
            "sme_log_event: Process signal 0x%.4X\n",
            CSR_GET_UINT16_FROM_LITTLE_ENDIAN(signal));


    /* If the signal is known, then do any filtering required, otherwise it pass it to the SME. */
    r = read_unpack_signal(signal, &unpacked_signal);
    if (r == CSR_RESULT_SUCCESS) {
        if ((unpacked_signal.SignalPrimitiveHeader.SignalId == CSR_DEBUG_STRING_INDICATION_ID) ||
            (unpacked_signal.SignalPrimitiveHeader.SignalId == CSR_DEBUG_WORD16_INDICATION_ID))
        {
            return;
        }
        if (unpacked_signal.SignalPrimitiveHeader.SignalId == CSR_MA_PACKET_INDICATION_ID)
        {
            u16 frmCtrl;
            u8 unicastPdu = TRUE;
            u8 *macHdrLocation;
            u8 *raddr = NULL, *taddr = NULL;
            CsrWifiMacAddress peerMacAddress;
            /* Check if we need to send CsrWifiRouterCtrlMicFailureInd*/
            CSR_MA_PACKET_INDICATION *ind = &unpacked_signal.u.MaPacketIndication;

            macHdrLocation = (u8 *) bulkdata->d[0].os_data_ptr;
            /* Fetch the frame control value from  mac header */
            frmCtrl = CSR_GET_UINT16_FROM_LITTLE_ENDIAN(macHdrLocation);

            /* Point to the addresses */
            raddr = macHdrLocation + MAC_HEADER_ADDR1_OFFSET;
            taddr = macHdrLocation + MAC_HEADER_ADDR2_OFFSET;

            memcpy(peerMacAddress.a, taddr, ETH_ALEN);

            if(ind->ReceptionStatus == CSR_MICHAEL_MIC_ERROR)
            {
                if (*raddr & 0x1)
                    unicastPdu = FALSE;

                CsrWifiRouterCtrlMicFailureIndSend (priv->CSR_WIFI_SME_IFACEQUEUE, 0,
                        (ind->VirtualInterfaceIdentifier & 0xff),peerMacAddress,
                        unicastPdu);
                return;
            }
            else
            {
                if(ind->ReceptionStatus == CSR_RX_SUCCESS)
                {
                    u8 pmBit = (frmCtrl & 0x1000)?0x01:0x00;
                    u16 interfaceTag = (ind->VirtualInterfaceIdentifier & 0xff);
                    CsrWifiRouterCtrlStaInfo_t *srcStaInfo =  CsrWifiRouterCtrlGetStationRecordFromPeerMacAddress(priv,taddr,interfaceTag);
                    if((srcStaInfo != NULL) && (uf_check_broadcast_bssid(priv, bulkdata)== FALSE))
                    {
                        uf_process_pm_bit_for_peer(priv,srcStaInfo,pmBit,interfaceTag);

                        /* Update station last activity flag */
                        srcStaInfo->activity_flag = TRUE;
                    }
                }
            }
        }

        if (unpacked_signal.SignalPrimitiveHeader.SignalId == CSR_MA_PACKET_CONFIRM_ID)
        {
            CSR_MA_PACKET_CONFIRM *cfm = &unpacked_signal.u.MaPacketConfirm;
            u16 interfaceTag = (cfm->VirtualInterfaceIdentifier & 0xff);
            netInterface_priv_t *interfacePriv;
            CSR_MA_PACKET_REQUEST *req;
            CsrWifiMacAddress peerMacAddress;

            if (interfaceTag >= CSR_WIFI_NUM_INTERFACES)
            {
                unifi_error(priv, "Bad MA_PACKET_CONFIRM interfaceTag %d\n", interfaceTag);
                return;
            }

            unifi_trace(priv,UDBG1,"MA-PACKET Confirm (%x, %x)\n", cfm->HostTag, cfm->TransmissionStatus);

            interfacePriv = priv->interfacePriv[interfaceTag];
#ifdef CSR_SUPPORT_SME
            if(interfacePriv->interfaceMode == CSR_WIFI_ROUTER_CTRL_MODE_AP ||
                 interfacePriv->interfaceMode == CSR_WIFI_ROUTER_CTRL_MODE_P2PGO) {

                if(cfm->HostTag == interfacePriv->multicastPduHostTag){
                    uf_process_ma_pkt_cfm_for_ap(priv ,interfaceTag, cfm);
                }
            }
#endif

            req = &interfacePriv->m4_signal.u.MaPacketRequest;

            if(cfm->HostTag & 0x80000000)
            {
                if (cfm->TransmissionStatus != CSR_TX_SUCCESSFUL)
                {
                    result = CSR_RESULT_FAILURE;
                }
#ifdef CSR_SUPPORT_SME
                memcpy(peerMacAddress.a, req->Ra.x, ETH_ALEN);
                /* Check if this is a confirm for EAPOL M4 frame and we need to send transmistted ind*/
                if (interfacePriv->m4_sent && (cfm->HostTag == interfacePriv->m4_hostTag))
                {
                    unifi_trace(priv, UDBG1, "%s: Sending M4 Transmit CFM\n", __FUNCTION__);
                    CsrWifiRouterCtrlM4TransmittedIndSend(priv->CSR_WIFI_SME_IFACEQUEUE, 0,
                            interfaceTag,
                            peerMacAddress,
                            result);
                    interfacePriv->m4_sent = FALSE;
                    interfacePriv->m4_hostTag = 0xffffffff;
                }
#endif
                /* If EAPOL was requested via router APIs then send cfm else ignore*/
                if((cfm->HostTag & 0x80000000) != CSR_WIFI_EAPOL_M4_HOST_TAG) {
                    CsrWifiRouterMaPacketCfmSend((u16)signal[2],
                        cfm->VirtualInterfaceIdentifier,
                        result,
                        (cfm->HostTag & 0x3fffffff), cfm->Rate);
                } else {
                    unifi_trace(priv, UDBG1, "%s: M4 received from netdevice\n", __FUNCTION__);
                }
                return;
            }
        }
    }

    mlmeCommand.length = signal_len;
    mlmeCommand.data = (u8*)signal;

    dataref1.length = bulkdata->d[0].data_length;
    if (dataref1.length > 0) {
        dataref1.data = (u8 *) bulkdata->d[0].os_data_ptr;
    } else
    {
        dataref1.data = NULL;
    }

    dataref2.length = bulkdata->d[1].data_length;
    if (dataref2.length > 0) {
        dataref2.data = (u8 *) bulkdata->d[1].os_data_ptr;
    } else
    {
        dataref2.data = NULL;
    }

    CsrWifiRouterCtrlHipIndSend(priv->CSR_WIFI_SME_IFACEQUEUE, mlmeCommand.length, mlmeCommand.data,
            dataref1.length, dataref1.data,
            dataref2.length, dataref2.data);

} /* sme_log_event() */


/*
 * ---------------------------------------------------------------------------
 * uf_sme_port_state
 *
 *      Return the state of the controlled port.
 *
 * Arguments:
 *      priv            Pointer to device private context struct
 *      address    Pointer to the destination for tx or sender for rx address
 *      queue           Controlled or uncontrolled queue
 *
 * Returns:
 *      An unifi_ControlledPortAction value.
 * ---------------------------------------------------------------------------
 */
CsrWifiRouterCtrlPortAction
uf_sme_port_state(unifi_priv_t *priv, unsigned char *address, int queue, u16 interfaceTag)
{
    int i;
    unifi_port_config_t *port;
    netInterface_priv_t *interfacePriv;

    if (interfaceTag >= CSR_WIFI_NUM_INTERFACES) {
        unifi_error(priv, "uf_sme_port_state: bad interfaceTag\n");
        return CSR_WIFI_ROUTER_CTRL_PORT_ACTION_8021X_PORT_CLOSED_DISCARD;
    }

    interfacePriv = priv->interfacePriv[interfaceTag];

    if (queue == UF_CONTROLLED_PORT_Q) {
        port = &interfacePriv->controlled_data_port;
    } else {
        port = &interfacePriv->uncontrolled_data_port;
    }

    if (!port->entries_in_use) {
        unifi_trace(priv, UDBG5, "No port configurations, return Discard.\n");
        return CSR_WIFI_ROUTER_CTRL_PORT_ACTION_8021X_PORT_CLOSED_DISCARD;
    }

    /* If the port configuration is common for all destinations, return it. */
    if (port->overide_action == UF_DATA_PORT_OVERIDE) {
        unifi_trace(priv, UDBG5, "Single port configuration (%d).\n",
                port->port_cfg[0].port_action);
        return port->port_cfg[0].port_action;
    }

    unifi_trace(priv, UDBG5, "Multiple (%d) port configurations.\n", port->entries_in_use);

    /* If multiple configurations exist.. */
    for (i = 0; i < UNIFI_MAX_CONNECTIONS; i++) {
        /* .. go through the list and match the destination address. */
        if (port->port_cfg[i].in_use &&
            memcmp(address, port->port_cfg[i].mac_address.a, ETH_ALEN) == 0) {
            /* Return the desired action. */
            return port->port_cfg[i].port_action;
        }
    }

    /* Could not find any information, return Open. */
    unifi_trace(priv, UDBG5, "port configuration not found, return Open.\n");
    return CSR_WIFI_ROUTER_CTRL_PORT_ACTION_8021X_PORT_OPEN;
} /* uf_sme_port_state() */

/*
 * ---------------------------------------------------------------------------
 * uf_sme_port_config_handle
 *
 *      Return the port config handle of the controlled/uncontrolled port.
 *
 * Arguments:
 *      priv            Pointer to device private context struct
 *      address    Pointer to the destination for tx or sender for rx address
 *      queue           Controlled or uncontrolled queue
 *
 * Returns:
 *      An  unifi_port_cfg_t* .
 * ---------------------------------------------------------------------------
 */
unifi_port_cfg_t*
uf_sme_port_config_handle(unifi_priv_t *priv, unsigned char *address, int queue, u16 interfaceTag)
{
    int i;
    unifi_port_config_t *port;
    netInterface_priv_t *interfacePriv = priv->interfacePriv[interfaceTag];

    if (interfaceTag >= CSR_WIFI_NUM_INTERFACES) {
        unifi_error(priv, "uf_sme_port_config_handle: bad interfaceTag\n");
        return NULL;
    }

    if (queue == UF_CONTROLLED_PORT_Q) {
        port = &interfacePriv->controlled_data_port;
    } else {
        port = &interfacePriv->uncontrolled_data_port;
    }

    if (!port->entries_in_use) {
        unifi_trace(priv, UDBG5, "No port configurations, return Discard.\n");
        return NULL;
    }

    /* If the port configuration is common for all destinations, return it. */
    if (port->overide_action == UF_DATA_PORT_OVERIDE) {
        unifi_trace(priv, UDBG5, "Single port configuration (%d).\n",
                port->port_cfg[0].port_action);
        if (address) {
            unifi_trace(priv, UDBG5, "addr[0] = %x, addr[1] = %x, addr[2] = %x, addr[3] = %x\n", address[0], address[1], address[2], address[3]);
        }
        return &port->port_cfg[0];
    }

    unifi_trace(priv, UDBG5, "Multiple port configurations.\n");

    /* If multiple configurations exist.. */
    for (i = 0; i < UNIFI_MAX_CONNECTIONS; i++) {
        /* .. go through the list and match the destination address. */
        if (port->port_cfg[i].in_use &&
            memcmp(address, port->port_cfg[i].mac_address.a, ETH_ALEN) == 0) {
            /* Return the desired action. */
            return &port->port_cfg[i];
        }
    }

    /* Could not find any information, return Open. */
    unifi_trace(priv, UDBG5, "port configuration not found, returning NULL (debug).\n");
    return NULL;
} /* uf_sme_port_config_handle */

void
uf_multicast_list_wq(struct work_struct *work)
{
    unifi_priv_t *priv = container_of(work, unifi_priv_t,
            multicast_list_task);
    int i;
    u16 interfaceTag = 0;
    CsrWifiMacAddress* multicast_address_list = NULL;
    int mc_count;
    u8 *mc_list;
    netInterface_priv_t *interfacePriv = priv->interfacePriv[interfaceTag];

    if (interfaceTag >= CSR_WIFI_NUM_INTERFACES) {
        unifi_error(priv, "uf_multicast_list_wq: bad interfaceTag\n");
        return;
    }

    unifi_trace(priv, UDBG5,
            "uf_multicast_list_wq: list count = %d\n",
            interfacePriv->mc_list_count);

    /* Flush the current list */
    CsrWifiRouterCtrlMulticastAddressIndSend(priv->CSR_WIFI_SME_IFACEQUEUE,0, interfaceTag, CSR_WIFI_SME_LIST_ACTION_FLUSH, 0, NULL);

    mc_count = interfacePriv->mc_list_count;
    mc_list = interfacePriv->mc_list;
    /*
     * Allocate a new list, need to free it later
     * in unifi_mgt_multicast_address_cfm().
     */
    multicast_address_list = kmalloc(mc_count * sizeof(CsrWifiMacAddress), GFP_KERNEL);

    if (multicast_address_list == NULL) {
        return;
    }

    for (i = 0; i < mc_count; i++) {
        memcpy(multicast_address_list[i].a, mc_list, ETH_ALEN);
        mc_list += ETH_ALEN;
    }

    if (priv->smepriv == NULL) {
        kfree(multicast_address_list);
        return;
    }

    CsrWifiRouterCtrlMulticastAddressIndSend(priv->CSR_WIFI_SME_IFACEQUEUE,0,
            interfaceTag,
            CSR_WIFI_SME_LIST_ACTION_ADD,
            mc_count, multicast_address_list);

    /* The SME will take a copy of the addreses*/
    kfree(multicast_address_list);
}


int unifi_cfg_power(unifi_priv_t *priv, unsigned char *arg)
{
    unifi_cfg_power_t cfg_power;
    int rc;
    int wol;

    if (get_user(cfg_power, (unifi_cfg_power_t*)(((unifi_cfg_command_t*)arg) + 1))) {
        unifi_error(priv, "UNIFI_CFG: Failed to get the argument\n");
        return -EFAULT;
    }

    switch (cfg_power) {
        case UNIFI_CFG_POWER_OFF:
            priv->wol_suspend = (enable_wol == UNIFI_WOL_OFF) ? FALSE : TRUE;
            rc = sme_sys_suspend(priv);
            if (rc) {
                return rc;
            }
            break;
        case UNIFI_CFG_POWER_ON:
            wol = priv->wol_suspend;
            rc = sme_sys_resume(priv);
            if (rc) {
                return rc;
            }
            if (wol) {
                /* Kick the BH to ensure pending transfers are handled when
                 * a suspend happened with card powered.
                 */
                unifi_send_signal(priv->card, NULL, 0, NULL);
            }
            break;
        default:
            unifi_error(priv, "WIFI POWER: Unknown value.\n");
            return -EINVAL;
    }

    return 0;
}


int unifi_cfg_power_save(unifi_priv_t *priv, unsigned char *arg)
{
    unifi_cfg_powersave_t cfg_power_save;
    CsrWifiSmePowerConfig powerConfig;
    int rc;

    if (get_user(cfg_power_save, (unifi_cfg_powersave_t*)(((unifi_cfg_command_t*)arg) + 1))) {
        unifi_error(priv, "UNIFI_CFG: Failed to get the argument\n");
        return -EFAULT;
    }

    /* Get the coex info from the SME */
    rc = sme_mgt_power_config_get(priv, &powerConfig);
    if (rc) {
        unifi_error(priv, "UNIFI_CFG: Get unifi_PowerConfigValue failed.\n");
        return rc;
    }

    switch (cfg_power_save) {
        case UNIFI_CFG_POWERSAVE_NONE:
            powerConfig.powerSaveLevel = CSR_WIFI_SME_POWER_SAVE_LEVEL_LOW;
            break;
        case UNIFI_CFG_POWERSAVE_FAST:
            powerConfig.powerSaveLevel = CSR_WIFI_SME_POWER_SAVE_LEVEL_MED;
            break;
        case UNIFI_CFG_POWERSAVE_FULL:
            powerConfig.powerSaveLevel = CSR_WIFI_SME_POWER_SAVE_LEVEL_HIGH;
            break;
        case UNIFI_CFG_POWERSAVE_AUTO:
            powerConfig.powerSaveLevel = CSR_WIFI_SME_POWER_SAVE_LEVEL_AUTO;
            break;
        default:
            unifi_error(priv, "POWERSAVE: Unknown value.\n");
            return -EINVAL;
    }

    rc = sme_mgt_power_config_set(priv, &powerConfig);

    if (rc) {
        unifi_error(priv, "UNIFI_CFG: Set unifi_PowerConfigValue failed.\n");
    }

    return rc;
}


int unifi_cfg_power_supply(unifi_priv_t *priv, unsigned char *arg)
{
    unifi_cfg_powersupply_t cfg_power_supply;
    CsrWifiSmeHostConfig hostConfig;
    int rc;

    if (get_user(cfg_power_supply, (unifi_cfg_powersupply_t*)(((unifi_cfg_command_t*)arg) + 1))) {
        unifi_error(priv, "UNIFI_CFG: Failed to get the argument\n");
        return -EFAULT;
    }

    /* Get the coex info from the SME */
    rc = sme_mgt_host_config_get(priv, &hostConfig);
    if (rc) {
        unifi_error(priv, "UNIFI_CFG: Get unifi_HostConfigValue failed.\n");
        return rc;
    }

    switch (cfg_power_supply) {
        case UNIFI_CFG_POWERSUPPLY_MAINS:
            hostConfig.powerMode = CSR_WIFI_SME_HOST_POWER_MODE_ACTIVE;
            break;
        case UNIFI_CFG_POWERSUPPLY_BATTERIES:
            hostConfig.powerMode = CSR_WIFI_SME_HOST_POWER_MODE_POWER_SAVE;
            break;
        default:
            unifi_error(priv, "POWERSUPPLY: Unknown value.\n");
            return -EINVAL;
    }

    rc = sme_mgt_host_config_set(priv, &hostConfig);
    if (rc) {
        unifi_error(priv, "UNIFI_CFG: Set unifi_HostConfigValue failed.\n");
    }

    return rc;
}


int unifi_cfg_packet_filters(unifi_priv_t *priv, unsigned char *arg)
{
    unsigned char *tclas_buffer;
    unsigned int tclas_buffer_length;
    tclas_t *dhcp_tclas;
    int rc;

    /* Free any TCLASs previously allocated */
    if (priv->packet_filters.tclas_ies_length) {
        kfree(priv->filter_tclas_ies);
        priv->filter_tclas_ies = NULL;
    }

    tclas_buffer = ((unsigned char*)arg) + sizeof(unifi_cfg_command_t) + sizeof(unsigned int);
    if (copy_from_user(&priv->packet_filters, (void*)tclas_buffer,
                sizeof(uf_cfg_bcast_packet_filter_t))) {
        unifi_error(priv, "UNIFI_CFG: Failed to get the filter struct\n");
        return -EFAULT;
    }

    tclas_buffer_length = priv->packet_filters.tclas_ies_length;

    /* Allocate TCLASs if necessary */
    if (priv->packet_filters.dhcp_filter) {
        priv->packet_filters.tclas_ies_length += sizeof(tclas_t);
    }
    if (priv->packet_filters.tclas_ies_length > 0) {
        priv->filter_tclas_ies = kmalloc(priv->packet_filters.tclas_ies_length, GFP_KERNEL);
        if (priv->filter_tclas_ies == NULL) {
            return -ENOMEM;
        }
        if (tclas_buffer_length) {
            tclas_buffer += sizeof(uf_cfg_bcast_packet_filter_t) - sizeof(unsigned char*);
            if (copy_from_user(priv->filter_tclas_ies,
                        tclas_buffer,
                        tclas_buffer_length)) {
                unifi_error(priv, "UNIFI_CFG: Failed to get the TCLAS buffer\n");
                return -EFAULT;
            }
        }
    }

    if(priv->packet_filters.dhcp_filter)
    {
        /* Append the DHCP tclas IE */
        dhcp_tclas = (tclas_t*)(priv->filter_tclas_ies + tclas_buffer_length);
        memset(dhcp_tclas, 0, sizeof(tclas_t));
        dhcp_tclas->element_id = 14;
        dhcp_tclas->length = sizeof(tcpip_clsfr_t) + 1;
        dhcp_tclas->user_priority = 0;
        dhcp_tclas->tcp_ip_cls_fr.cls_fr_type = 1;
        dhcp_tclas->tcp_ip_cls_fr.version = 4;
        ((u8*)(&dhcp_tclas->tcp_ip_cls_fr.source_port))[0] = 0x00;
        ((u8*)(&dhcp_tclas->tcp_ip_cls_fr.source_port))[1] = 0x44;
        ((u8*)(&dhcp_tclas->tcp_ip_cls_fr.dest_port))[0] = 0x00;
        ((u8*)(&dhcp_tclas->tcp_ip_cls_fr.dest_port))[1] = 0x43;
        dhcp_tclas->tcp_ip_cls_fr.protocol = 0x11;
        dhcp_tclas->tcp_ip_cls_fr.cls_fr_mask = 0x58; //bits: 3,4,6
    }

    rc = sme_mgt_packet_filter_set(priv);

    return rc;
}


int unifi_cfg_wmm_qos_info(unifi_priv_t *priv, unsigned char *arg)
{
    u8 wmm_qos_info;
    int rc = 0;

    if (get_user(wmm_qos_info, (u8*)(((unifi_cfg_command_t*)arg) + 1))) {
        unifi_error(priv, "UNIFI_CFG: Failed to get the argument\n");
        return -EFAULT;
    }

    /* Store the value in the connection info */
    priv->connection_config.wmmQosInfo = wmm_qos_info;

    return rc;
}


int unifi_cfg_wmm_addts(unifi_priv_t *priv, unsigned char *arg)
{
    u32 addts_tid;
    u8 addts_ie_length;
    u8 *addts_ie;
    u8 *addts_params;
    CsrWifiSmeDataBlock tspec;
    CsrWifiSmeDataBlock tclas;
    int rc;

    addts_params = (u8*)(((unifi_cfg_command_t*)arg) + 1);
    if (get_user(addts_tid, (u32*)addts_params)) {
        unifi_error(priv, "unifi_cfg_wmm_addts: Failed to get the argument\n");
        return -EFAULT;
    }

    addts_params += sizeof(u32);
    if (get_user(addts_ie_length, (u8*)addts_params)) {
        unifi_error(priv, "unifi_cfg_wmm_addts: Failed to get the argument\n");
        return -EFAULT;
    }

    unifi_trace(priv, UDBG4, "addts: tid = 0x%x ie_length = %d\n",
            addts_tid, addts_ie_length);

    addts_ie = kmalloc(addts_ie_length, GFP_KERNEL);
    if (addts_ie == NULL) {
        unifi_error(priv,
                "unifi_cfg_wmm_addts: Failed to malloc %d bytes for addts_ie buffer\n",
                addts_ie_length);
        return -ENOMEM;
    }

    addts_params += sizeof(u8);
    rc = copy_from_user(addts_ie, addts_params, addts_ie_length);
    if (rc) {
        unifi_error(priv, "unifi_cfg_wmm_addts: Failed to get the addts buffer\n");
        kfree(addts_ie);
        return -EFAULT;
    }

    tspec.data = addts_ie;
    tspec.length = addts_ie_length;
    tclas.data = NULL;
    tclas.length = 0;

    rc = sme_mgt_tspec(priv, CSR_WIFI_SME_LIST_ACTION_ADD, addts_tid,
            &tspec, &tclas);

    kfree(addts_ie);
    return rc;
}


int unifi_cfg_wmm_delts(unifi_priv_t *priv, unsigned char *arg)
{
    u32 delts_tid;
    u8 *delts_params;
    CsrWifiSmeDataBlock tspec;
    CsrWifiSmeDataBlock tclas;
    int rc;

    delts_params = (u8*)(((unifi_cfg_command_t*)arg) + 1);
    if (get_user(delts_tid, (u32*)delts_params)) {
        unifi_error(priv, "unifi_cfg_wmm_delts: Failed to get the argument\n");
        return -EFAULT;
    }

    unifi_trace(priv, UDBG4, "delts: tid = 0x%x\n", delts_tid);

    tspec.data = tclas.data = NULL;
    tspec.length = tclas.length = 0;

    rc = sme_mgt_tspec(priv, CSR_WIFI_SME_LIST_ACTION_REMOVE, delts_tid,
            &tspec, &tclas);

    return rc;
}

int unifi_cfg_strict_draft_n(unifi_priv_t *priv, unsigned char *arg)
{
    u8 strict_draft_n;
    u8 *strict_draft_n_params;
    int rc;

    CsrWifiSmeStaConfig  staConfig;
    CsrWifiSmeDeviceConfig  deviceConfig;

    strict_draft_n_params = (u8*)(((unifi_cfg_command_t*)arg) + 1);
    if (get_user(strict_draft_n, (u8*)strict_draft_n_params)) {
        unifi_error(priv, "unifi_cfg_strict_draft_n: Failed to get the argument\n");
        return -EFAULT;
    }

    unifi_trace(priv, UDBG4, "strict_draft_n: = %s\n", ((strict_draft_n) ? "yes":"no"));

    rc = sme_mgt_sme_config_get(priv, &staConfig, &deviceConfig);

    if (rc) {
        unifi_warning(priv, "unifi_cfg_strict_draft_n: Get unifi_SMEConfigValue failed.\n");
        return -EFAULT;
    }

    deviceConfig.enableStrictDraftN = strict_draft_n;

    rc = sme_mgt_sme_config_set(priv, &staConfig, &deviceConfig);
    if (rc) {
        unifi_warning(priv, "unifi_cfg_strict_draft_n: Set unifi_SMEConfigValue failed.\n");
        rc = -EFAULT;
    }

    return rc;
}


int unifi_cfg_enable_okc(unifi_priv_t *priv, unsigned char *arg)
{
    u8 enable_okc;
    u8 *enable_okc_params;
    int rc;

    CsrWifiSmeStaConfig staConfig;
    CsrWifiSmeDeviceConfig deviceConfig;

    enable_okc_params = (u8*)(((unifi_cfg_command_t*)arg) + 1);
    if (get_user(enable_okc, (u8*)enable_okc_params)) {
        unifi_error(priv, "unifi_cfg_enable_okc: Failed to get the argument\n");
        return -EFAULT;
    }

    unifi_trace(priv, UDBG4, "enable_okc: = %s\n", ((enable_okc) ? "yes":"no"));

    rc = sme_mgt_sme_config_get(priv, &staConfig, &deviceConfig);
    if (rc) {
        unifi_warning(priv, "unifi_cfg_enable_okc: Get unifi_SMEConfigValue failed.\n");
        return -EFAULT;
    }

    staConfig.enableOpportunisticKeyCaching = enable_okc;

    rc = sme_mgt_sme_config_set(priv, &staConfig, &deviceConfig);
    if (rc) {
        unifi_warning(priv, "unifi_cfg_enable_okc: Set unifi_SMEConfigValue failed.\n");
        rc = -EFAULT;
    }

    return rc;
}


int unifi_cfg_get_info(unifi_priv_t *priv, unsigned char *arg)
{
    unifi_cfg_get_t get_cmd;
    char inst_name[IFNAMSIZ];
    int rc;

    if (get_user(get_cmd, (unifi_cfg_get_t*)(((unifi_cfg_command_t*)arg) + 1))) {
        unifi_error(priv, "UNIFI_CFG: Failed to get the argument\n");
        return -EFAULT;
    }

    switch (get_cmd) {
        case UNIFI_CFG_GET_COEX:
            {
                CsrWifiSmeCoexInfo coexInfo;
                /* Get the coex info from the SME */
                rc = sme_mgt_coex_info_get(priv, &coexInfo);
                if (rc) {
                    unifi_error(priv, "UNIFI_CFG: Get unifi_CoexInfoValue failed.\n");
                    return rc;
                }

                /* Copy the info to the out buffer */
                if (copy_to_user((void*)arg,
                            &coexInfo,
                            sizeof(CsrWifiSmeCoexInfo))) {
                    unifi_error(priv, "UNIFI_CFG: Failed to copy the coex info\n");
                    return -EFAULT;
                }
                break;
            }
        case UNIFI_CFG_GET_POWER_MODE:
            {
                CsrWifiSmePowerConfig powerConfig;
                rc = sme_mgt_power_config_get(priv, &powerConfig);
                if (rc) {
                    unifi_error(priv, "UNIFI_CFG: Get unifi_PowerConfigValue failed.\n");
                    return rc;
                }

                /* Copy the info to the out buffer */
                if (copy_to_user((void*)arg,
                            &powerConfig.powerSaveLevel,
                            sizeof(CsrWifiSmePowerSaveLevel))) {
                    unifi_error(priv, "UNIFI_CFG: Failed to copy the power save info\n");
                    return -EFAULT;
                }
                break;
            }
        case UNIFI_CFG_GET_POWER_SUPPLY:
            {
                CsrWifiSmeHostConfig hostConfig;
                rc = sme_mgt_host_config_get(priv, &hostConfig);
                if (rc) {
                    unifi_error(priv, "UNIFI_CFG: Get unifi_HostConfigValue failed.\n");
                    return rc;
                }

                /* Copy the info to the out buffer */
                if (copy_to_user((void*)arg,
                            &hostConfig.powerMode,
                            sizeof(CsrWifiSmeHostPowerMode))) {
                    unifi_error(priv, "UNIFI_CFG: Failed to copy the host power mode\n");
                    return -EFAULT;
                }
                break;
            }
        case UNIFI_CFG_GET_VERSIONS:
            break;
        case UNIFI_CFG_GET_INSTANCE:
            {
                u16 InterfaceId=0;
                uf_net_get_name(priv->netdev[InterfaceId], &inst_name[0], sizeof(inst_name));

                /* Copy the info to the out buffer */
                if (copy_to_user((void*)arg,
                            &inst_name[0],
                            sizeof(inst_name))) {
                    unifi_error(priv, "UNIFI_CFG: Failed to copy the instance name\n");
                    return -EFAULT;
                }
            }
            break;

        case UNIFI_CFG_GET_AP_CONFIG:
            {
#ifdef CSR_SUPPORT_WEXT_AP
                uf_cfg_ap_config_t cfg_ap_config;

		memset(&cfg_ap_config, 0, sizeof(cfg_ap_config));
                cfg_ap_config.channel = priv->ap_config.channel;
                cfg_ap_config.beaconInterval = priv->ap_mac_config.beaconInterval;
                cfg_ap_config.wmmEnabled = priv->ap_mac_config.wmmEnabled;
                cfg_ap_config.dtimPeriod = priv->ap_mac_config.dtimPeriod;
                cfg_ap_config.phySupportedBitmap = priv->ap_mac_config.phySupportedBitmap;
                if (copy_to_user((void*)arg,
                            &cfg_ap_config,
                            sizeof(uf_cfg_ap_config_t))) {
                    unifi_error(priv, "UNIFI_CFG: Failed to copy the AP configuration\n");
                    return -EFAULT;
                }
#else
                   return -EPERM;
#endif
            }
            break;


        default:
            unifi_error(priv, "unifi_cfg_get_info: Unknown value.\n");
            return -EINVAL;
    }

    return 0;
}
#ifdef CSR_SUPPORT_WEXT_AP
int
 uf_configure_supported_rates(u8 * supportedRates, u8 phySupportedBitmap)
{
    int i=0;
    u8 b=FALSE, g = FALSE, n = FALSE;
    b = phySupportedBitmap & CSR_WIFI_SME_AP_PHY_SUPPORT_B;
    n = phySupportedBitmap & CSR_WIFI_SME_AP_PHY_SUPPORT_N;
    g = phySupportedBitmap & CSR_WIFI_SME_AP_PHY_SUPPORT_G;
    if(b || g) {
        supportedRates[i++]=0x82;
        supportedRates[i++]=0x84;
        supportedRates[i++]=0x8b;
        supportedRates[i++]=0x96;
    } else if(n) {
        /* For some strange reasons WiFi stack needs both b and g rates*/
        supportedRates[i++]=0x02;
        supportedRates[i++]=0x04;
        supportedRates[i++]=0x0b;
        supportedRates[i++]=0x16;
        supportedRates[i++]=0x0c;
        supportedRates[i++]=0x12;
        supportedRates[i++]=0x18;
	supportedRates[i++]=0x24;
        supportedRates[i++]=0x30;
        supportedRates[i++]=0x48;
        supportedRates[i++]=0x60;
        supportedRates[i++]=0x6c;
    }
    if(g) {
        if(!b) {
            supportedRates[i++]=0x8c;
            supportedRates[i++]=0x98;
            supportedRates[i++]=0xb0;
        } else {
            supportedRates[i++]=0x0c;
            supportedRates[i++]=0x18;
            supportedRates[i++]=0x30;
        }
        supportedRates[i++]=0x48;
        supportedRates[i++]=0x12;
        supportedRates[i++]=0x24;
        supportedRates[i++]=0x60;
        supportedRates[i++]=0x6c;
    }
    return i;
}
int unifi_cfg_set_ap_config(unifi_priv_t * priv,unsigned char* arg)
{
    uf_cfg_ap_config_t cfg_ap_config;
    char *buffer;

    buffer = ((unsigned char*)arg) + sizeof(unifi_cfg_command_t) + sizeof(unsigned int);
    if (copy_from_user(&cfg_ap_config, (void*)buffer,
                sizeof(uf_cfg_ap_config_t))) {
        unifi_error(priv, "UNIFI_CFG: Failed to get the ap config struct\n");
        return -EFAULT;
    }
    priv->ap_config.channel = cfg_ap_config.channel;
    priv->ap_mac_config.dtimPeriod = cfg_ap_config.dtimPeriod;
    priv->ap_mac_config.beaconInterval = cfg_ap_config.beaconInterval;
    priv->group_sec_config.apGroupkeyTimeout = cfg_ap_config.groupkeyTimeout;
    priv->group_sec_config.apStrictGtkRekey = cfg_ap_config.strictGtkRekeyEnabled;
    priv->group_sec_config.apGmkTimeout = cfg_ap_config.gmkTimeout;
    priv->group_sec_config.apResponseTimeout = cfg_ap_config.responseTimeout;
    priv->group_sec_config.apRetransLimit = cfg_ap_config.retransLimit;

    priv->ap_mac_config.shortSlotTimeEnabled = cfg_ap_config.shortSlotTimeEnabled;
    priv->ap_mac_config.ctsProtectionType=cfg_ap_config.ctsProtectionType;

    priv->ap_mac_config.wmmEnabled = cfg_ap_config.wmmEnabled;

    priv->ap_mac_config.apHtParams.rxStbc=cfg_ap_config.rxStbc;
    priv->ap_mac_config.apHtParams.rifsModeAllowed=cfg_ap_config.rifsModeAllowed;

    priv->ap_mac_config.phySupportedBitmap = cfg_ap_config.phySupportedBitmap;
    priv->ap_mac_config.maxListenInterval=cfg_ap_config.maxListenInterval;

    priv->ap_mac_config.supportedRatesCount=     uf_configure_supported_rates(priv->ap_mac_config.supportedRates,priv->ap_mac_config.phySupportedBitmap);

    return 0;
}

#endif
#ifdef CSR_SUPPORT_WEXT

    void
uf_sme_config_wq(struct work_struct *work)
{
    CsrWifiSmeStaConfig  staConfig;
    CsrWifiSmeDeviceConfig  deviceConfig;
    unifi_priv_t *priv = container_of(work, unifi_priv_t, sme_config_task);

    /* Register to receive indications from the SME */
    CsrWifiSmeEventMaskSetReqSend(0,
            CSR_WIFI_SME_INDICATIONS_WIFIOFF | CSR_WIFI_SME_INDICATIONS_CONNECTIONQUALITY |
            CSR_WIFI_SME_INDICATIONS_MEDIASTATUS | CSR_WIFI_SME_INDICATIONS_MICFAILURE);

    if (sme_mgt_sme_config_get(priv, &staConfig, &deviceConfig)) {
        unifi_warning(priv, "uf_sme_config_wq: Get unifi_SMEConfigValue failed.\n");
        return;
    }

    if (priv->if_index == CSR_INDEX_5G) {
        staConfig.ifIndex = CSR_WIFI_SME_RADIO_IF_GHZ_5_0;
    } else {
        staConfig.ifIndex = CSR_WIFI_SME_RADIO_IF_GHZ_2_4;
    }

    deviceConfig.trustLevel = (CsrWifiSme80211dTrustLevel)tl_80211d;
    if (sme_mgt_sme_config_set(priv, &staConfig, &deviceConfig)) {
        unifi_warning(priv,
                "SME config for 802.11d Trust Level and Radio Band failed.\n");
        return;
    }

} /* uf_sme_config_wq() */

#endif /* CSR_SUPPORT_WEXT */


/*
 * ---------------------------------------------------------------------------
 *  uf_ta_ind_wq
 *
 *      Deferred work queue function to send Traffic Analysis protocols
 *      indications to the SME.
 *      These are done in a deferred work queue for two reasons:
 *       - the CsrWifiRouterCtrl...Send() functions are not safe for atomic context
 *       - we want to load the main driver data path as lightly as possible
 *
 *      The TA classifications already come from a workqueue.
 *
 *  Arguments:
 *      work    Pointer to work queue item.
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
    void
uf_ta_ind_wq(struct work_struct *work)
{
    struct ta_ind *ind = container_of(work, struct ta_ind, task);
    unifi_priv_t *priv = container_of(ind, unifi_priv_t, ta_ind_work);
    u16 interfaceTag = 0;


    CsrWifiRouterCtrlTrafficProtocolIndSend(priv->CSR_WIFI_SME_IFACEQUEUE,0,
            interfaceTag,
            ind->packet_type,
            ind->direction,
            ind->src_addr);
    ind->in_use = 0;

} /* uf_ta_ind_wq() */


/*
 * ---------------------------------------------------------------------------
 *  uf_ta_sample_ind_wq
 *
 *      Deferred work queue function to send Traffic Analysis sample
 *      indications to the SME.
 *      These are done in a deferred work queue for two reasons:
 *       - the CsrWifiRouterCtrl...Send() functions are not safe for atomic context
 *       - we want to load the main driver data path as lightly as possible
 *
 *      The TA classifications already come from a workqueue.
 *
 *  Arguments:
 *      work    Pointer to work queue item.
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
    void
uf_ta_sample_ind_wq(struct work_struct *work)
{
    struct ta_sample_ind *ind = container_of(work, struct ta_sample_ind, task);
    unifi_priv_t *priv = container_of(ind, unifi_priv_t, ta_sample_ind_work);
    u16 interfaceTag = 0;

     unifi_trace(priv, UDBG5, "rxtcp %d txtcp %d rxudp %d txudp %d prio %d\n",
        priv->rxTcpThroughput,
        priv->txTcpThroughput,
        priv->rxUdpThroughput,
        priv->txUdpThroughput,
        priv->bh_thread.prio);

    if(priv->rxTcpThroughput > 1000)
    {
        if (bh_priority == -1 && priv->bh_thread.prio != 1)
        {
            struct sched_param param;
            priv->bh_thread.prio = 1;
            unifi_trace(priv, UDBG1, "%s new thread (RT) priority = %d\n",
                        priv->bh_thread.name, priv->bh_thread.prio);
            param.sched_priority = priv->bh_thread.prio;
            sched_setscheduler(priv->bh_thread.thread_task, SCHED_FIFO, &param);
        }
    } else
    {
        if (bh_priority == -1 && priv->bh_thread.prio != DEFAULT_PRIO)
        {
            struct sched_param param;
            param.sched_priority = 0;
            sched_setscheduler(priv->bh_thread.thread_task, SCHED_NORMAL, &param);
            priv->bh_thread.prio = DEFAULT_PRIO;
            unifi_trace(priv, UDBG1, "%s new thread priority = %d\n",
                        priv->bh_thread.name, priv->bh_thread.prio);
            set_user_nice(priv->bh_thread.thread_task, PRIO_TO_NICE(priv->bh_thread.prio));
        }
    }

    CsrWifiRouterCtrlTrafficSampleIndSend(priv->CSR_WIFI_SME_IFACEQUEUE,0, interfaceTag, ind->stats);

    ind->in_use = 0;

} /* uf_ta_sample_ind_wq() */


/*
 * ---------------------------------------------------------------------------
 *  uf_send_m4_ready_wq
 *
 *      Deferred work queue function to send M4 ReadyToSend inds to the SME.
 *      These are done in a deferred work queue for two reasons:
 *       - the CsrWifiRouterCtrl...Send() functions are not safe for atomic context
 *       - we want to load the main driver data path as lightly as possible
 *
 *  Arguments:
 *      work    Pointer to work queue item.
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
void
uf_send_m4_ready_wq(struct work_struct *work)
{
    netInterface_priv_t *InterfacePriv = container_of(work, netInterface_priv_t, send_m4_ready_task);
    u16 iface = InterfacePriv->InterfaceTag;
    unifi_priv_t *priv = InterfacePriv->privPtr;
    CSR_MA_PACKET_REQUEST *req = &InterfacePriv->m4_signal.u.MaPacketRequest;
    CsrWifiMacAddress peer;
    unsigned long flags;

    /* The peer address was stored in the signal */
    spin_lock_irqsave(&priv->m4_lock, flags);
    memcpy(peer.a, req->Ra.x, sizeof(peer.a));
    spin_unlock_irqrestore(&priv->m4_lock, flags);

    /* Send a signal to SME */
    CsrWifiRouterCtrlM4ReadyToSendIndSend(priv->CSR_WIFI_SME_IFACEQUEUE, 0, iface, peer);

	unifi_trace(priv, UDBG1, "M4ReadyToSendInd sent for peer %pMF\n",
		peer.a);

} /* uf_send_m4_ready_wq() */

#if (defined(CSR_WIFI_SECURITY_WAPI_ENABLE) && defined(CSR_WIFI_SECURITY_WAPI_SW_ENCRYPTION))
/*
 * ---------------------------------------------------------------------------
 *  uf_send_pkt_to_encrypt
 *
 *      Deferred work queue function to send the WAPI data pkts to SME when unicast KeyId = 1
 *      These are done in a deferred work queue for two reasons:
 *       - the CsrWifiRouterCtrl...Send() functions are not safe for atomic context
 *       - we want to load the main driver data path as lightly as possible
 *
 *  Arguments:
 *      work    Pointer to work queue item.
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
void uf_send_pkt_to_encrypt(struct work_struct *work)
{
    netInterface_priv_t *interfacePriv = container_of(work, netInterface_priv_t, send_pkt_to_encrypt);
    u16 interfaceTag = interfacePriv->InterfaceTag;
    unifi_priv_t *priv = interfacePriv->privPtr;

    u32 pktBulkDataLength;
    u8 *pktBulkData;
    unsigned long flags;

    if (interfacePriv->interfaceMode == CSR_WIFI_ROUTER_CTRL_MODE_STA) {

        pktBulkDataLength = interfacePriv->wapi_unicast_bulk_data.data_length;

        if (pktBulkDataLength > 0) {
		    pktBulkData = kmalloc(pktBulkDataLength, GFP_KERNEL);
	    } else {
		    unifi_error(priv, "uf_send_pkt_to_encrypt() : invalid buffer\n");
		    return;
	    }

        spin_lock_irqsave(&priv->wapi_lock, flags);
        /* Copy over the MA PKT REQ bulk data */
        memcpy(pktBulkData, (u8*)interfacePriv->wapi_unicast_bulk_data.os_data_ptr, pktBulkDataLength);
        /* Free any bulk data buffers allocated for the WAPI Data pkt */
        unifi_net_data_free(priv, &interfacePriv->wapi_unicast_bulk_data);
        interfacePriv->wapi_unicast_bulk_data.net_buf_length = 0;
        interfacePriv->wapi_unicast_bulk_data.data_length = 0;
        interfacePriv->wapi_unicast_bulk_data.os_data_ptr = interfacePriv->wapi_unicast_bulk_data.os_net_buf_ptr = NULL;
        spin_unlock_irqrestore(&priv->wapi_lock, flags);

        CsrWifiRouterCtrlWapiUnicastTxEncryptIndSend(priv->CSR_WIFI_SME_IFACEQUEUE, 0, interfaceTag, pktBulkDataLength, pktBulkData);
        unifi_trace(priv, UDBG1, "WapiUnicastTxEncryptInd sent to SME\n");

        kfree(pktBulkData); /* Would have been copied over by the SME Handler */

    } else {
	    unifi_warning(priv, "uf_send_pkt_to_encrypt() is NOT applicable for interface mode - %d\n",interfacePriv->interfaceMode);
    }
}/* uf_send_pkt_to_encrypt() */
#endif