blob: f33fb8747bc3184defc2c4a5d5dcef829a1ece1c [file] [log] [blame]
/*
* Copyright (c) 2015 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.
*/
/**
* DOC: hif_napi.c
*
* HIF NAPI interface implementation
*/
#include <string.h> /* memset */
#include <cds_api.h>
#include <hif_napi.h>
#include <hif_debug.h>
#include <hif_io32.h>
#include <ce_api.h>
enum napi_decision_vector {
HIF_NAPI_NOEVENT = 0,
HIF_NAPI_INITED = 1,
HIF_NAPI_CONF_UP = 2
};
#define ENABLE_NAPI_MASK (HIF_NAPI_INITED | HIF_NAPI_CONF_UP)
/**
* hif_napi_create() - creates the NAPI structures for a given CE
* @hif : pointer to hif context
* @pipe_id: the CE id on which the instance will be created
* @poll : poll function to be used for this NAPI instance
* @budget : budget to be registered with the NAPI instance
* @scale : scale factor on the weight (to scaler budget to 1000)
*
* Description:
* Creates NAPI instances. This function is called
* unconditionally during initialization. It creates
* napi structures through the proper HTC/HIF calls.
* The structures are disabled on creation.
* Note that for each NAPI instance a separate dummy netdev is used
*
* Return:
* < 0: error
* = 0: <should never happen>
* > 0: id of the created object (for multi-NAPI, number of objects created)
*/
int hif_napi_create(struct ol_softc *hif,
uint8_t pipe_id,
int (*poll)(struct napi_struct *, int),
int budget,
int scale)
{
struct qca_napi_data *napid;
struct qca_napi_info *napii;
NAPI_DEBUG("-->(pipe=%d, budget=%d, scale=%d)\n",
pipe_id, budget, scale);
NAPI_DEBUG("hif->napi_data.state = 0x%08x\n",
hif->napi_data.state);
NAPI_DEBUG("hif->napi_data.ce_map = 0x%08x\n",
hif->napi_data.ce_map);
napid = &(hif->napi_data);
if (0 == (napid->state & HIF_NAPI_INITED)) {
memset(napid, 0, sizeof(struct qca_napi_data));
mutex_init(&(napid->mutex));
init_dummy_netdev(&(napid->netdev));
napid->state |= HIF_NAPI_INITED;
HIF_INFO("%s: NAPI structures initialized\n", __func__);
NAPI_DEBUG("NAPI structures initialized\n");
}
napii = &(napid->napis[pipe_id]);
memset(napii, 0, sizeof(struct qca_napi_info));
napii->scale = scale;
napii->id = NAPI_PIPE2ID(pipe_id);
NAPI_DEBUG("adding napi=%p to netdev=%p (poll=%p, bdgt=%d)\n",
&(napii->napi), &(napid->netdev), poll, budget);
netif_napi_add(&(napid->netdev), &(napii->napi), poll, budget);
NAPI_DEBUG("after napi_add\n");
NAPI_DEBUG("napi=0x%p, netdev=0x%p\n",
&(napii->napi), &(napid->netdev));
NAPI_DEBUG("napi.dev_list.prev=0x%p, next=0x%p\n",
napii->napi.dev_list.prev, napii->napi.dev_list.next);
NAPI_DEBUG("dev.napi_list.prev=0x%p, next=0x%p\n",
napid->netdev.napi_list.prev, napid->netdev.napi_list.next);
/* It is OK to change the state variable below without protection
as there should be no-one around yet */
napid->ce_map |= (0x01 << pipe_id);
HIF_INFO("%s: NAPI id %d created for pipe %d\n", __func__,
napii->id, pipe_id);
NAPI_DEBUG("NAPI id %d created for pipe %d\n", napii->id, pipe_id);
NAPI_DEBUG("<--napi_id=%d]\n", napii->id);
return napii->id;
}
/**
*
* hif_napi_destroy() - destroys the NAPI structures for a given instance
* @hif : pointer to hif context
* @ce_id : the CE id whose napi instance will be destroyed
* @force : if set, will destroy even if entry is active (de-activates)
*
* Description:
* Destroy a given NAPI instance. This function is called
* unconditionally during cleanup.
* Refuses to destroy an entry of it is still enabled (unless force=1)
* Marks the whole napi_data invalid if all instances are destroyed.
*
* Return:
* -EINVAL: specific entry has not been created
* -EPERM : specific entry is still active
* 0 < : error
* 0 = : success
*/
int hif_napi_destroy(struct ol_softc *hif,
uint8_t id,
int force)
{
uint8_t ce = NAPI_ID2PIPE(id);
int rc = 0;
NAPI_DEBUG("-->(id=%d, force=%d)\n", id, force);
if (0 == (hif->napi_data.state & HIF_NAPI_INITED)) {
HIF_ERROR("%s: NAPI not initialized or entry %d not created\n",
__func__, id);
rc = -EINVAL;
} else if (0 == (hif->napi_data.ce_map & (0x01 << ce))) {
HIF_ERROR("%s: NAPI instance %d (pipe %d) not created\n",
__func__, id, ce);
rc = -EINVAL;
} else {
struct qca_napi_data *napid;
struct qca_napi_info *napii;
napid = &(hif->napi_data);
napii = &(napid->napis[ce]);
if (hif->napi_data.state == HIF_NAPI_CONF_UP) {
if (force) {
napi_disable(&(napii->napi));
HIF_INFO("%s: NAPI entry %d force disabled\n",
__func__, id);
NAPI_DEBUG("NAPI %d force disabled\n", id);
} else {
HIF_ERROR("%s: Cannot destroy active NAPI %d\n",
__func__, id);
rc = -EPERM;
}
}
if (0 == rc) {
NAPI_DEBUG("before napi_del\n");
NAPI_DEBUG("napi.dlist.prv=0x%p, next=0x%p\n",
napii->napi.dev_list.prev,
napii->napi.dev_list.next);
NAPI_DEBUG("dev.napi_l.prv=0x%p, next=0x%p\n",
napid->netdev.napi_list.prev,
napid->netdev.napi_list.next);
netif_napi_del(&(napii->napi));
napid->ce_map &= ~(0x01 << ce);
napii->scale = 0;
HIF_INFO("%s: NAPI %d destroyed\n", __func__, id);
/* if there are no active instances and
if they are all destroyed,
set the whole structure to uninitialized state */
if (napid->ce_map == 0) {
/* hif->napi_data.state = 0; */
memset(napid,
0, sizeof(struct qca_napi_data));
HIF_INFO("%s: no NAPI instances. Zapped.\n",
__func__);
}
}
}
return rc;
}
/**
*
* hif_napi_get_all() - returns the address of the whole HIF NAPI structure
* @hif: pointer to hif context
*
* Description:
* Returns the address of the whole structure
*
* Return:
* <addr>: address of the whole HIF NAPI structure
*/
inline struct qca_napi_data *hif_napi_get_all(struct ol_softc *hif)
{
return &(hif->napi_data);
}
/**
*
* hif_napi_event() - Decision-maker to enable/disable NAPI.
* @hif : pointer to hif context
* @evnt: event that has been detected
* @data: more data regarding the event
*
* Description:
* This function decides whether or not NAPI should be enabled.
* NAPI will be enabled, if all the following is satisfied.
* 1- has been enabled administratively:
* the .ini file has the enabled setting and it has not been disabled
* by an vendor command override later
*
* Return:
* < 0: some error
* = 0: NAPI is now disabled
* = 1: NAPI is now enabled
*/
int hif_napi_event(struct ol_softc *hif, enum qca_napi_event event, void *data)
{
int rc;
uint32_t prev_state;
int i;
struct napi_struct *napi;
NAPI_DEBUG("-->(event=%d, aux=%p)\n", event, data);
mutex_lock(&(hif->napi_data.mutex));
prev_state = hif->napi_data.state;
switch (event) {
case NAPI_EVT_INI_FILE:
case NAPI_EVT_CMD_STATE: {
int on = (data != ((void *)0));
HIF_INFO("%s: received evnt: CONF %s; v = %d (state=0x%0x)\n",
__func__,
(event == NAPI_EVT_INI_FILE)?".ini file":"cmd",
on, prev_state);
if (on)
if (prev_state & HIF_NAPI_CONF_UP) {
HIF_INFO("%s: duplicate NAPI conf ON msg\n",
__func__);
} else {
HIF_INFO("%s: setting configuration to ON\n",
__func__);
hif->napi_data.state |= HIF_NAPI_CONF_UP;
}
else /* off request */
if (prev_state & HIF_NAPI_CONF_UP) {
HIF_INFO("%s: setting configuration to OFF\n",
__func__);
hif->napi_data.state &= ~HIF_NAPI_CONF_UP;
} else {
HIF_INFO("%s: duplicate NAPI conf OFF msg\n",
__func__);
}
break;
}
/* case NAPI_INIT_FILE/CMD_STATE */
default: {
HIF_ERROR("%s: unknown event: %d (data=0x%0lx)\n",
__func__, event, (unsigned long) data);
break;
} /* default */
}; /* switch */
mutex_unlock(&(hif->napi_data.mutex));
if (prev_state != hif->napi_data.state) {
if (hif->napi_data.state == ENABLE_NAPI_MASK) {
rc = 1;
for (i = 0; i < CE_COUNT_MAX; i++)
if ((hif->napi_data.ce_map & (0x01 << i))) {
napi = &(hif->napi_data.napis[i].napi);
NAPI_DEBUG("enabling NAPI %d\n", i);
napi_enable(napi);
}
} else {
rc = 0;
for (i = 0; i < CE_COUNT_MAX; i++)
if (hif->napi_data.ce_map & (0x01 << i)) {
napi = &(hif->napi_data.napis[i].napi);
NAPI_DEBUG("disabling NAPI %d\n", i);
napi_disable(napi);
}
}
} else {
HIF_INFO("%s: no change in hif napi state (still %d)\n",
__func__, prev_state);
rc = (hif->napi_data.state == ENABLE_NAPI_MASK);
}
NAPI_DEBUG("<--[rc=%d]\n", rc);
return rc;
}
/**
* hif_napi_enabled() - checks whether NAPI is enabled for given ce or not
* @hif: hif context
* @ce : CE instance (or -1, to check if any CEs are enabled)
*
* Return: bool
*/
inline int hif_napi_enabled(struct ol_softc *hif, int ce)
{
int rc;
if (-1 == ce)
rc = ((hif->napi_data.state == ENABLE_NAPI_MASK));
else
rc = ((hif->napi_data.state == ENABLE_NAPI_MASK) &&
(hif->napi_data.ce_map & (0x01 << ce)));
return rc;
};
/**
* hif_napi_enable_irq() - enables bus interrupts after napi_complete
*
* @hif: hif context
* @id : id of NAPI instance calling this (used to determine the CE)
*
* Return: void
*/
inline void hif_napi_enable_irq(struct ol_softc *hif, int id)
{
ce_irq_enable(hif, NAPI_ID2PIPE(id));
return;
}
/**
* hif_napi_schedule() - schedules napi, updates stats
* @scn: hif context
* @ce_id: index of napi instance
*
* Return: void
*/
int hif_napi_schedule(struct ol_softc *scn, int ce_id)
{
int cpu = smp_processor_id();
scn->napi_data.napis[ce_id].stats[cpu].napi_schedules++;
NAPI_DEBUG("scheduling napi %d (ce:%d)\n",
scn->napi_data.napis[ce_id].id, ce_id);
napi_schedule(&(scn->napi_data.napis[ce_id].napi));
return true;
}
/**
* hif_napi_poll() - NAPI poll routine
* @napi : pointer to NAPI struct as kernel holds it
* @budget:
*
* This is the body of the poll function.
* The poll function is called by kernel. So, there is a wrapper
* function in HDD, which in turn calls this function.
* Two main reasons why the whole thing is not implemented in HDD:
* a) references to things like ce_service that HDD is not aware of
* b) proximity to the implementation of ce_tasklet, which the body
* of this function should be very close to.
*
* NOTE TO THE MAINTAINER:
* Consider this function and ce_tasklet very tightly coupled pairs.
* Any changes to ce_tasklet or this function may likely need to be
* reflected in the counterpart.
*
* Returns:
* int: the amount of work done in this poll ( <= budget)
*/
int hif_napi_poll(struct napi_struct *napi, int budget)
{
int rc, normalized, bucket;
int cpu = smp_processor_id();
struct ol_softc *hif;
struct qca_napi_info *napi_info;
NAPI_DEBUG("%s -->(.., budget=%d)\n", budget);
napi_info = (struct qca_napi_info *)
container_of(napi, struct qca_napi_info, napi);
napi_info->stats[cpu].napi_polls++;
hif = (struct ol_softc *)cds_get_context(CDF_MODULE_ID_HIF);
CDF_ASSERT(hif != NULL);
rc = ce_per_engine_service(hif, NAPI_ID2PIPE(napi_info->id));
HIF_INFO_HI("%s: ce_per_engine_service reports %d msgs processed",
__func__, rc);
napi_info->stats[cpu].napi_workdone += rc;
normalized = (rc / napi_info->scale);
/* do not return 0, if there was some work done,
even if it is below the scale */
if (rc)
normalized++;
bucket = (normalized / QCA_NAPI_DEF_SCALE);
napi_info->stats[cpu].napi_budget_uses[bucket]++;
/* if ce_per engine reports 0, then we should make sure
poll is terminated */
if (0 == rc)
NAPI_DEBUG("%s:%d: nothing processed by CE. Completing NAPI\n",
__func__, __LINE__);
if (rc <= HIF_NAPI_MAX_RECEIVES) {
napi_info->stats[cpu].napi_completes++;
/* enable interrupts */
napi_complete(napi);
hif_napi_enable_irq(hif, napi_info->id);
/* support suspend/resume */
cdf_atomic_dec(&(hif->active_tasklet_cnt));
NAPI_DEBUG("%s:%d: napi_complete + enabling the interrupts\n",
__func__, __LINE__);
}
NAPI_DEBUG("%s <--[normalized=%d]\n", _func__, normalized);
return normalized;
}