blob: 5fdad1ec1d4f3f7874a067274571ed727ea892fb [file] [log] [blame]
/*
* Copyright (c) 2014-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
/*========================================================================
\file epping_main.c
\brief WLAN End Point Ping test tool implementation
========================================================================*/
/*--------------------------------------------------------------------------
Include Files
------------------------------------------------------------------------*/
#include <wlan_hdd_includes.h>
#include <vos_api.h>
#include <vos_sched.h>
#include <linux/etherdevice.h>
#include <linux/firmware.h>
#include <wcnss_api.h>
#include <wlan_hdd_tx_rx.h>
#include <wniApi.h>
#include <wlan_nlink_srv.h>
#include <wlan_hdd_cfg.h>
#include <wlan_ptt_sock_svc.h>
#include <wlan_hdd_wowl.h>
#include <wlan_hdd_misc.h>
#include <wlan_hdd_wext.h>
#include <linux/wireless.h>
#include <net/cfg80211.h>
#include "vos_cnss.h"
#include <linux/rtnetlink.h>
#include <linux/semaphore.h>
#include <linux/ctype.h>
#include <wlan_hdd_hostapd.h>
#include <wlan_hdd_softap_tx_rx.h>
#include "epping_main.h"
#include "epping_internal.h"
static int epping_start_adapter(epping_adapter_t *pAdapter);
static void epping_stop_adapter(epping_adapter_t *pAdapter);
static void epping_timer_expire(void *data)
{
struct net_device *dev = (struct net_device *) data;
epping_adapter_t *pAdapter;
if (dev == NULL) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: netdev = NULL", __func__);
return;
}
pAdapter = netdev_priv(dev);
if (pAdapter == NULL) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: adapter = NULL", __func__);
return;
}
pAdapter->epping_timer_state = EPPING_TX_TIMER_STOPPED;
epping_tx_timer_expire(pAdapter);
}
static int epping_ndev_open(struct net_device *dev)
{
epping_adapter_t *pAdapter;
int ret = 0;
pAdapter = netdev_priv(dev);
epping_start_adapter(pAdapter);
return ret;
}
static int epping_ndev_stop(struct net_device *dev)
{
epping_adapter_t *pAdapter;
int ret = 0;
pAdapter = netdev_priv(dev);
if (NULL == pAdapter) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: EPPING adapter context is Null", __func__);
ret = -ENODEV;
goto end;
}
epping_stop_adapter(pAdapter);
end:
return ret;
}
static void epping_ndev_uninit (struct net_device *dev)
{
epping_adapter_t *pAdapter;
pAdapter = netdev_priv(dev);
if (NULL == pAdapter) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: EPPING adapter context is Null", __func__);
goto end;
}
epping_stop_adapter(pAdapter);
end:
return;
}
void epping_tx_queue_timeout(struct net_device *dev)
{
epping_adapter_t *pAdapter;
pAdapter = netdev_priv(dev);
if (NULL == pAdapter) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: EPPING adapter context is Null", __func__);
goto end;
}
EPPING_LOG(VOS_TRACE_LEVEL_ERROR,
"%s: Transmission timeout occurred, pAdapter->started= %d",
__func__, pAdapter->started);
/* Getting here implies we disabled the TX queues
* for too long. Since this is epping
* (not because of disassociation or low resource scenarios),
* try to restart the queue
*/
if (pAdapter->started)
netif_wake_queue(dev);
end:
return;
}
int epping_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
epping_adapter_t *pAdapter;
int ret = 0;
pAdapter = netdev_priv(dev);
if (NULL == pAdapter) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: EPPING adapter context is Null", __func__);
ret = -ENODEV;
goto end;
}
ret = epping_tx_send(skb, pAdapter);
end:
return ret;
}
struct net_device_stats* epping_get_stats(struct net_device *dev)
{
epping_adapter_t *pAdapter = netdev_priv(dev);
if ( NULL == pAdapter )
{
EPPING_LOG(VOS_TRACE_LEVEL_ERROR, "%s: pAdapter = NULL", __func__);
return NULL;
}
return &pAdapter->stats;
}
int epping_ndev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
epping_adapter_t *pAdapter;
int ret = 0;
pAdapter = netdev_priv(dev);
if (NULL == pAdapter) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: EPPING adapter context is Null", __func__);
ret = -ENODEV;
goto end;
}
if (dev != pAdapter->dev) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: HDD adapter/dev inconsistency", __func__);
ret = -ENODEV;
goto end;
}
if ((!ifr) || (!ifr->ifr_data)) {
ret = -EINVAL;
goto end;
}
switch (cmd) {
case (SIOCDEVPRIVATE + 1):
EPPING_LOG(VOS_TRACE_LEVEL_ERROR,
"%s: do not support ioctl %d (SIOCDEVPRIVATE + 1)",
__func__, cmd);
break;
default:
EPPING_LOG(VOS_TRACE_LEVEL_ERROR, "%s: unknown ioctl %d",
__func__, cmd);
ret = -EINVAL;
break;
}
end:
return ret;
}
static int epping_set_mac_address(struct net_device *dev, void *addr)
{
epping_adapter_t *pAdapter = netdev_priv(dev);
struct sockaddr *psta_mac_addr = addr;
vos_mem_copy(&pAdapter->macAddressCurrent,
psta_mac_addr->sa_data, ETH_ALEN);
vos_mem_copy(dev->dev_addr, psta_mac_addr->sa_data, ETH_ALEN);
return 0;
}
static void epping_stop_adapter(epping_adapter_t *pAdapter)
{
struct device *dev;
if (pAdapter && pAdapter->started) {
EPPING_LOG(LOG1, FL("Disabling queues"));
netif_tx_disable(pAdapter->dev);
netif_carrier_off(pAdapter->dev);
pAdapter->started = false;
dev = pAdapter->pEpping_ctx->parent_dev;
if (dev)
vos_request_bus_bandwidth(dev, CNSS_BUS_WIDTH_LOW);
}
}
static int epping_start_adapter(epping_adapter_t *pAdapter)
{
struct device *dev;
if (!pAdapter) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: pAdapter= NULL\n", __func__);
return -1;
}
if (!pAdapter->started) {
dev = pAdapter->pEpping_ctx->parent_dev;
if (dev)
vos_request_bus_bandwidth(dev, CNSS_BUS_WIDTH_HIGH);
netif_carrier_on(pAdapter->dev);
EPPING_LOG(LOG1, FL("Enabling queues"));
netif_tx_start_all_queues(pAdapter->dev);
pAdapter->started = true;
} else {
EPPING_LOG(VOS_TRACE_LEVEL_WARN,
"%s: pAdapter %pK already started\n", __func__, pAdapter);
}
return 0;
}
static int epping_register_adapter(epping_adapter_t *pAdapter)
{
int ret = 0;
if ((ret = register_netdev(pAdapter->dev)) != 0) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: unable to register device\n", pAdapter->dev->name);
} else {
pAdapter->registered = true;
}
return ret;
}
static void epping_unregister_adapter(epping_adapter_t *pAdapter)
{
if (pAdapter) {
epping_stop_adapter(pAdapter);
if (pAdapter->registered) {
unregister_netdev(pAdapter->dev);
pAdapter->registered = false;
}
} else {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: pAdapter = NULL, unable to unregister device\n",
__func__);
}
}
void epping_destroy_adapter(epping_adapter_t *pAdapter)
{
struct net_device *dev = NULL;
epping_context_t *pEpping_ctx;
if (!pAdapter || !pAdapter->pEpping_ctx) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: pAdapter = NULL\n", __func__);
return;
}
dev = pAdapter->dev;
pEpping_ctx= pAdapter->pEpping_ctx;
epping_unregister_adapter(pAdapter);
adf_os_spinlock_destroy(&pAdapter->data_lock);
adf_os_timer_free(&pAdapter->epping_timer);
pAdapter->epping_timer_state = EPPING_TX_TIMER_STOPPED;
while (adf_nbuf_queue_len(&pAdapter->nodrop_queue)) {
adf_nbuf_t tmp_nbuf = NULL;
tmp_nbuf = adf_nbuf_queue_remove(&pAdapter->nodrop_queue);
if (tmp_nbuf)
adf_nbuf_free(tmp_nbuf);
}
free_netdev(dev);
if (!pEpping_ctx)
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: pEpping_ctx = NULL\n", __func__);
else
pEpping_ctx->epping_adapter = NULL;
}
static struct net_device_ops epping_drv_ops = {
.ndo_open = epping_ndev_open,
.ndo_stop = epping_ndev_stop,
.ndo_uninit = epping_ndev_uninit,
.ndo_start_xmit = epping_hard_start_xmit,
.ndo_tx_timeout = epping_tx_queue_timeout,
.ndo_get_stats = epping_get_stats,
.ndo_do_ioctl = epping_ndev_ioctl,
.ndo_set_mac_address = epping_set_mac_address,
.ndo_select_queue = NULL,
};
#define EPPING_TX_QUEUE_MAX_LEN 128 /* need to be power of 2 */
epping_adapter_t *epping_add_adapter(epping_context_t *pEpping_ctx,
tSirMacAddr macAddr, device_mode_t device_mode)
{
struct net_device *dev;
epping_adapter_t *pAdapter;
dev = alloc_netdev(sizeof(epping_adapter_t),
"wifi%d",
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)) || defined(WITH_BACKPORTS)
NET_NAME_UNKNOWN,
#endif
ether_setup);
if (dev == NULL) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"%s: Cannot allocate epping_adapter_t\n", __func__);
return NULL;
}
pAdapter = netdev_priv(dev);
vos_mem_zero(pAdapter, sizeof(*pAdapter));
pAdapter->dev = dev;
pAdapter->pEpping_ctx = pEpping_ctx;
pAdapter->device_mode = device_mode; /* station, SAP, etc */
vos_mem_copy(dev->dev_addr, (void *)macAddr, sizeof(tSirMacAddr));
vos_mem_copy(pAdapter->macAddressCurrent.bytes,
macAddr, sizeof(tSirMacAddr));
adf_os_spinlock_init(&pAdapter->data_lock);
adf_nbuf_queue_init(&pAdapter->nodrop_queue);
pAdapter->epping_timer_state = EPPING_TX_TIMER_STOPPED;
adf_os_timer_init(epping_get_adf_ctx(), &pAdapter->epping_timer,
epping_timer_expire, dev, ADF_DEFERRABLE_TIMER);
dev->type = ARPHRD_IEEE80211;
dev->netdev_ops = &epping_drv_ops;
dev->watchdog_timeo = 5 * HZ; /* XXX */
dev->tx_queue_len = ATH_TXBUF-1; /* 1 for mgmt frame */
if (epping_register_adapter(pAdapter) == 0) {
EPPING_LOG(LOG1, FL("Disabling queues"));
netif_tx_disable(dev);
netif_carrier_off(dev);
return pAdapter;
} else {
epping_destroy_adapter(pAdapter);
return NULL;
}
}
int epping_connect_service(epping_context_t *pEpping_ctx)
{
int status, i;
HTC_SERVICE_CONNECT_REQ connect;
HTC_SERVICE_CONNECT_RESP response;
vos_mem_zero(&connect, sizeof(connect));
vos_mem_zero(&response, sizeof(response));
/* these fields are the same for all service endpoints */
connect.EpCallbacks.pContext = pEpping_ctx;
connect.EpCallbacks.EpTxCompleteMultiple = epping_tx_complete_multiple;
connect.EpCallbacks.EpRecv = epping_rx;
/* epping_tx_complete use Multiple version */
connect.EpCallbacks.EpTxComplete = NULL;
connect.MaxSendQueueDepth = 64;
#ifdef HIF_SDIO
connect.EpCallbacks.EpRecvRefill = epping_refill;
connect.EpCallbacks.EpSendFull =
epping_tx_queue_full /* ar6000_tx_queue_full */;
#elif defined(HIF_USB) || defined(HIF_PCI)
connect.EpCallbacks.EpRecvRefill = NULL /* provided by HIF */;
connect.EpCallbacks.EpSendFull = NULL /* provided by HIF */;
/* disable flow control for hw flow control */
connect.ConnectionFlags |= HTC_CONNECT_FLAGS_DISABLE_CREDIT_FLOW_CTRL;
#endif
/* connect to service */
connect.ServiceID = WMI_DATA_BE_SVC;
status = HTCConnectService(pEpping_ctx->HTCHandle, &connect, &response);
if (status != EOK) {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"Failed to connect to Endpoint Ping BE service status:%d \n",
status);
return -1;;
} else {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"eppingtest BE endpoint:%d\n", response.Endpoint);
}
pEpping_ctx->EppingEndpoint[0] = response.Endpoint;
#if defined(HIF_PCI) || defined(HIF_USB)
connect.ServiceID = WMI_DATA_BK_SVC;
status = HTCConnectService(pEpping_ctx->HTCHandle,
&connect, &response);
if (status != EOK)
{
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"Failed to connect to Endpoint Ping BK service status:%d \n",
status);
return -1;;
} else {
EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
"eppingtest BK endpoint:%d\n", response.Endpoint);
}
pEpping_ctx->EppingEndpoint[1] = response.Endpoint;
/* Since we do not create other two SVC use BK endpoint
* for rest ACs (2, 3) */
for (i = 2; i < EPPING_MAX_NUM_EPIDS; i++) {
pEpping_ctx->EppingEndpoint[i] = response.Endpoint;
}
#else
/* we only use one endpoint for high latenance bus.
* Map all AC's EPIDs to the same endpoint ID returned by HTC */
for (i = 0; i < EPPING_MAX_NUM_EPIDS; i++) {
pEpping_ctx->EppingEndpoint[i] = response.Endpoint;
}
#endif
return 0;
}