blob: 24d48f28b2bdb88d3487485da0825670253d14a0 [file] [log] [blame]
/*
* Copyright (c) 2009 Mindspeed Technologies, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
*/
#include "fpp.h"
#include "modules.h"
#include "channels.h"
#include "events.h"
#include "module_tx.h"
#include "system.h"
#include "fpart.h"
#include "fe.h"
#include "module_timer.h"
#include "module_qm.h"
#include "module_tx.h"
#include "module_hidrv.h"
#include "module_expt.h"
#include "asm/bitops.h"
#define FFS(x) __fls(x)
//#define QM_DEBUG
// The global stat array is initialized to invalid values, to force the initial state to be
// written to the class PEs at initialization time.
static u8 qm_global_stat[GEM_PORTS] = {0xFF, 0xFF, 0xFF};
/** Convert qos context to Big-endian.
* This function copies the parameters of the qos context
* into another context in BE format before sending it to PFE.
*
* @param le_context QOS control context in LE format.
* @param be_context QOS context in BE format.
*
*/
static void qm_convert_context_to_be (PQM_context_ctl le_context, PQM_context be_context)
{
int i, j;
memset (be_context , 0, sizeof(QM_context));
#if defined(CONFIG_PLATFORM_PCI)
be_context->tmu_id = le_context->tmu_id + 2;
#else
be_context->tmu_id = le_context->tmu_id;
#endif
be_context->num_hw_shapers = le_context->num_hw_shapers;
be_context->num_sw_shapers = le_context->num_sw_shapers;
be_context->num_sched = le_context->num_sched;
be_context->chip_revision = CHIP_REVISION();
for (i = 0; i < NUM_HW_SHAPERS; i++)
{
if (le_context->hw_shaper_ctl[i].enable)
{
be_context->hw_shaper[i].qmask = cpu_to_be32(le_context->hw_shaper[i].qmask);
be_context->hw_shaper[i].shaper_rate = cpu_to_be32(le_context->hw_shaper[i].shaper_rate);
}
}
for (i = 0; i < NUM_SW_SHAPERS; i++)
{
if (le_context->sw_shaper_ctl[i].enable)
{
be_context->sw_shaper[i].qmask = cpu_to_be32(le_context->sw_shaper[i].qmask);
be_context->sw_shaper[i].shaper_rate = cpu_to_be32(le_context->sw_shaper[i].shaper_rate);
}
}
for (i =0 ; i < NUM_SCHEDULERS; i++)
{
be_context->sched[i].qmask = cpu_to_be32(le_context->sched[i].qmask);
be_context->sched[i].alg = le_context->sched[i].alg;
be_context->sched[i].numqueues = le_context->sched[i].numqueues;
for (j = 0; j < MAX_SCHEDULER_QUEUES; j++)
{
be_context->sched[i].queue_list[j] = le_context->sched[i].queue_list[j];
}
}
for (i = 0; i < NUM_QUEUES; i++)
{
be_context->weight[i] = cpu_to_be16(le_context->weight[i]);
be_context->q_len_masks[i] = cpu_to_be32(le_context->q_len_masks[i]);
be_context->qresult_regoffset[i] = le_context->qresult_regoffset[i];
}
return;
}
/** Convert qos context to Big-endian.
* This function copies the parameters of the qos context
* into another context in BE format into the LMEM.
*
* @param le_context QOS control context in LE format.
* @param be_shaper Shaper control structure in BE format. (char array)
*
*/
static void qm_convert_shaper_to_be(PQM_context_ctl le_context, u8* be_shaper)
{
PQM_ShaperDesc_ctl le_shaper = le_context->hw_shaper_ctl;
PQM_ShaperDesc_ctl temp = (PQM_ShaperDesc_ctl) (be_shaper);
int i;
memset(temp, 0 , QM_CMD_SIZE);
for (i = 0; i < NUM_HW_SHAPERS; i++, temp++,le_shaper++)
{
temp->enable = le_shaper->enable;
temp->int_wt = le_shaper->int_wt;
temp->frac_wt = cpu_to_be16(le_shaper->frac_wt);
temp->max_credit = cpu_to_be32(le_shaper->max_credit);
temp->clk_div = cpu_to_be32(le_shaper->clk_div);
}
return;
}
/** Update the shaper information in the qos context.
* This function updates the shaper configuration in QOS context.
* It updates the shaper's qmask based on the queues
* assigned to the shaper.
*
* Note that this function is only called for the GEM (hw) shapers.
*
* @param qm_context_ctl Qos context control structure.
*
*/
static void QM_update_shaper(PQM_context_ctl qm_context_ctl)
{
int i;
PQM_QDesc pq;
for (i = 0; i < NUM_HW_SHAPERS; i++)
qm_context_ctl->hw_shaper[i].qmask = 0;
for (i = 0; i < NUM_SW_SHAPERS; i++)
qm_context_ctl->sw_shaper[i].qmask = 0;
for (i = 0; i < NUM_QUEUES; i++)
{
int shaper_num;
pq = &qm_context_ctl->q[i];
shaper_num = pq->shaper_num;
if (shaper_num >= NUM_SHAPERS)
continue;
qm_context_ctl->hw_shaper[shaper_num].qmask |= 1 << i;
}
if (qm_context_ctl->hw_shaper_ctl[PORT_SHAPER_INDEX].enable)
qm_context_ctl->hw_shaper[PORT_SHAPER_INDEX].qmask = PORT_SHAPER_QMASK;
qm_context_ctl->num_hw_shapers = 0;
for (i = 0; i < PORT_SHAPER_INDEX; i++)
{
if (qm_context_ctl->hw_shaper_ctl[i].enable == TRUE)
qm_context_ctl->num_hw_shapers = i + 1;
}
qm_context_ctl->num_sw_shapers = 0;
for (i = 0; i < NUM_SW_SHAPERS; i++)
{
if (qm_context_ctl->sw_shaper_ctl[i].enable == TRUE)
qm_context_ctl->num_sw_shapers = i + 1;
}
return;
}
/** Update the scheduler information in the qos context.
* This function updates the scheduler configuration in QOS context.
* It updates the scheduler's qmask based on the queues
* assigned to the shaper.
*
* @param qm_context_ctl Qos context control structure.
*
*/
static void QM_update_scheduler(PQM_context_ctl qm_context_ctl)
{
int i, num_sched = 0;
PQM_QDesc pq;
// update schedulers
qm_context_ctl->sched_mask = 0;
for (i = 0; i < NUM_SCHEDULERS; i++)
{
qm_context_ctl->sched[i].qmask = 0;
qm_context_ctl->sched[i].numqueues = 0;
}
for (i = 0; i < NUM_QUEUES; i++)
{
int sched_num;
pq = &qm_context_ctl->q[i];
sched_num = pq->sched_num;
if (sched_num >= NUM_SCHEDULERS)
continue;
qm_context_ctl->sched[sched_num].qmask |= 1 << i;
qm_context_ctl->sched[sched_num].queue_list[qm_context_ctl->sched[sched_num].numqueues] = i;
qm_context_ctl->sched_mask |= 1 << sched_num;
qm_context_ctl->sched[sched_num].numqueues++;
}
for (i = 0; i < NUM_SCHEDULERS; i++)
{
if (qm_context_ctl->sched[i].qmask != 0)
num_sched = i + 1;
}
qm_context_ctl->num_sched = num_sched;
#ifdef QM_DEBUG
printk(KERN_INFO "global enable for scheds: %x-%x-%x-%x for tmu %d\n", (unsigned int)TMU_TDQ0_SCH_CTRL, (unsigned int) TMU_TDQ1_SCH_CTRL , (unsigned int) TMU_TDQ2_SCH_CTRL , (unsigned int) TMU_TDQ3_SCH_CTRL, qm_context_ctl->tmu_id);
#endif
/* Global Enable for the schedulers on Phy */
switch(qm_context_ctl->tmu_id)
{
case TMU0_ID:
writel(qm_context_ctl->sched_mask, (void*)TMU_TDQ0_SCH_CTRL);
break;
case TMU1_ID:
writel(qm_context_ctl->sched_mask, (void*)TMU_TDQ1_SCH_CTRL);
break;
case TMU2_ID:
writel(qm_context_ctl->sched_mask,(void*) TMU_TDQ2_SCH_CTRL);
break;
case TMU3_ID:
writel(qm_context_ctl->sched_mask, (void*)TMU_TDQ3_SCH_CTRL);
break;
}
return;
}
static int QM_update_qos_enabled_status(u32 port_index, u8 qos_status)
{
struct pfe_ctrl *ctrl = &pfe->ctrl;
int id;
if (port_index > 2 )
{
printk (KERN_ERR "%s: Invalid portindex %d\n", __func__, port_index);
return -1;
}
// only update class PEs if status changes
if (qm_global_stat[port_index] == qos_status)
return NO_ERR;
qm_global_stat[port_index] = qos_status;
if (pe_sync_stop(ctrl, CLASS_MASK) < 0)
return CMD_ERR;
if(qos_status)
phy_port[port_index].flags |= QOS_ENABLED;
else
phy_port[port_index].flags &= ~QOS_ENABLED;
/* update the DMEM in class-pe */
for (id = CLASS0_ID; id <= CLASS_MAX_ID; id++)
{
pe_dmem_writeb(id, phy_port[port_index].flags, virt_to_class_dmem(&phy_port[port_index].flags));
}
pe_start(ctrl, CLASS_MASK);
return NO_ERR;
}
/** Send the context update request to PFE TMU.
* This function updates the QOS context to TMU and sends the request
* to TMU to configure the shaper and scheduler registers. It converts
* the little-endian structures to big-endian before copying to PE's memory.
*
* @param qm_context_ctl Qos context control structure.
*
*/
static int QM_update_TMU(PQM_context_ctl qm_context_ctl, u32 flags)
{
struct pfe_ctrl *ctrl = &pfe->ctrl;
qm_convert_context_to_be(qm_context_ctl, &g_qm_context);
if (flags & SHAPER_CONFIG)
{
qm_convert_shaper_to_be(qm_context_ctl, g_qm_cmd_info);
pe_dmem_memcpy_to32(qm_context_ctl->tmu_id, virt_to_tmu_dmem(&g_qm_cmd_info), &g_qm_cmd_info, QM_CMD_SIZE);
}
/* Write the context to TMU's DMEM */
pe_dmem_memcpy_to32(qm_context_ctl->tmu_id, virt_to_tmu_dmem(&g_qm_context), &g_qm_context, sizeof (QM_context));
#ifdef QM_DEBUG
printk (KERN_INFO "Sending context update command to TMU \n");
#endif
if (tmu_pe_request(ctrl,qm_context_ctl->tmu_id, flags) < 0)
return CMD_ERR ;
return NO_ERR;
}
/** Update qlenmasks.
* This function updates the q_len_masks based the scheduler and shaper
* configured for the queue.
*
* @param qm_context_ctl Qos control context structure.
*
*/
static void QM_update_qlenmask(PQM_context_ctl qm_context_ctl)
{
int i;
for (i = 0; i < NUM_QUEUES; i++)
{
/* q_len_masks[i] :
* 1) says if 0th queue is winner,
In which all schedulers and shapers,
* Length should be written.
* 2) q_len_masks[i][7:0] = scheduler_len_mask
* 3) q_len_masks[i][17:8] = shaper_len_mask
*/
qm_context_ctl->q_len_masks[i] = (1 << qm_context_ctl->q[i].sched_num);
if ((qm_context_ctl->q[i].shaper_num >= 0) && (qm_context_ctl->q[i].shaper_num < NUM_SHAPERS))
qm_context_ctl->q_len_masks[i] |= (1 << (NUM_SCHEDULERS + qm_context_ctl->q[i].shaper_num));
if (qm_context_ctl->hw_shaper_ctl[PORT_SHAPER_INDEX].enable)
qm_context_ctl->q_len_masks[i] |= (1 << (NUM_SCHEDULERS + PORT_SHAPER_INDEX));
}
}
/** Update qdepth to the PFE H/W.
* This function updates the qdepth parameters to the
* Hardware registers
*
* @param qm_context_ctl Qos context control structure.
*
*/
static void QM_update_qdepth(PQM_context_ctl qm_context_ctl)
{
int i;
u32 qdepth;
for (i =0; i < NUM_QUEUES; i++)
{
qdepth = qm_context_ctl->max_qdepth[i];
// LOG: 68855
// The following is a workaround for the reordered packet and BMU2 buffer leakage issue.
if (CHIP_REVISION() == 0)
qdepth = 31; // ignore configured value, and force all qdepths to be 31 for now
if (qdepth)
{
/* Write the phyno and queueno to CTL register */
/* current phyno : 8:11 , qno: 0:7 */
writel(((qm_context_ctl->port << 8) | i) ,(void*) TMU_TEQ_CTRL);
/* Enable the tail drop for the queues */
// writel( TEQ_HTD , (void*)TMU_TEQ_QCFG);
/* Write the qmax value to the probability 82:103 bits
i.e (82:95)first 14 bits in HW_PROB_CFG2 register
and (96:103) 8 bits in HW_PROB_CFG3 register */
writel( (qdepth & 0x3fff) << 18 , (void*)TMU_TEQ_HW_PROB_CFG2);
writel( (qdepth & 0x3fffff) >> 14 , (void*)TMU_TEQ_HW_PROB_CFG3);
}
}
return;
}
/** Update inter-frame-gap for a TMU in PFE H/W registers.
* This function updates the ifg parameter configured
* in the PFE hardware register for a TMU.
*
* @param qm_context_ctl Qos context control structure.
*
*/
static void QM_update_ifg(PQM_context_ctl qm_context_ctl)
{
u32 temp;
u8* val;
temp = readl((void*)TMU_TDQ_IIFG_CFG);
val = (u8*) &temp;
val[qm_context_ctl->port] = qm_context_ctl->ifg;
writel(temp, (void*)TMU_TDQ_IIFG_CFG);
#ifdef QM_DEBUG
printk(KERN_INFO "Updating ifg at addr: %x port: %d val = %x\n", (unsigned int)TMU_TDQ_IIFG_CFG , qm_context_ctl->port, temp);
#endif
return;
}
/** Update the qos control context with configuration change.
* This function updates the QOS control context based on the
* configuration change and control flags passed.
*
* @param index port index (GEM0 , GEM1, GEM2)
* @param qm_ctl_flags The control flag denoting the type of action
* to be performed.
*
*/
static int QM_update_context(u32 index, u32 qm_ctl_flags)
{
int rtn_code = NO_ERR;
PQM_context_ctl qm_context_ctl;
if (qm_global_stat[index] == QM_DISABLE)
qm_context_ctl = QM_GET_QOSOFF_CONTEXT(index);
else
qm_context_ctl = QM_GET_CONTEXT(index);
if (qm_ctl_flags & QDEPTH_CONFIG)
QM_update_qdepth(qm_context_ctl);
if (qm_ctl_flags & IFG_CONFIG)
QM_update_ifg(qm_context_ctl);
if (qm_ctl_flags & SCHEDULER_CONFIG)
QM_update_scheduler(qm_context_ctl);
if (qm_ctl_flags & SHAPER_CONFIG)
QM_update_shaper(qm_context_ctl);
if (qm_ctl_flags & (SCHEDULER_CONFIG | SHAPER_CONFIG))
QM_update_qlenmask(qm_context_ctl);
// update the TMU
if (qm_ctl_flags & (SCHEDULER_CONFIG | SHAPER_CONFIG))
rtn_code = QM_update_TMU(qm_context_ctl, qm_ctl_flags & (SCHEDULER_CONFIG | SHAPER_CONFIG));
return rtn_code;
}
/** Updates the hardware phyqueues .
* This function initializes the hardware phy queues
* in the qos context
*
* @param qm_context_ctl Qos control context structure.
*
*/
static void QM_hwqueue_init(PQM_context_ctl qm_context_ctl)
{
int i;
/*
Queue result mapping:
TMU0: QUEUE_RESULT0, QUEUE_RESULT1, QUEUE_RESULT2 --> PHY0_INQ_ADDR
TMU1: QUEUE_RESULT0, QUEUE_RESULT1, QUEUE_RESULT2 --> PHY1_INQ_ADDR
TMU2: QUEUE_RESULT0, QUEUE_RESULT1, QUEUE_RESULT2 --> PHY2_INQ_ADDR
TMU3: QUEUE_RESULT0 --> PHY3_INQ_ADDR,
QUEUE_RESULT1 --> PHY4_INQ_ADDR,
QUEUE_RESULT2 --> PHY5_INQ_ADDR
Initialized by host:
PHY0_INQ_ADDR = GPI0
PHY1_INQ_ADDR = GPI1
PHY2_INQ_ADDR = GPI2
PHY3_INQ_ADDR = HIF
PHY4_INQ_ADDR = HIFNCPY
PHY5_INQ_ADDR = UTIL-PE
*/
if (qm_context_ctl->tmu_id == TMU3_ID)
{
/* TMU 3 Queues 0-3 => Util PE*/
for (i = 0; i <= 3; i++)
qm_context_ctl->qresult_regoffset[i] = QUEUE_RESULT2_REGOFFSET;
/* TMU 3 Queues 4-14 => HIF */
for (i = 4; i <= 14; i++)
qm_context_ctl->qresult_regoffset[i] = QUEUE_RESULT0_REGOFFSET;
qm_context_ctl->qresult_regoffset[TMU_QUEUE_RTP_CUTTHRU] = QUEUE_RESULT1_REGOFFSET;
}
else
{
for (i = 0; i < NUM_QUEUES; i++)
qm_context_ctl->qresult_regoffset[i] = QUEUE_RESULT0_REGOFFSET;
}
}
/** Resets to the default configuration for a context.
* This function resets the QM control context to
* its default configuration.
*
* @param qm_context_ctl QOS control context stucture
* @param index port index (GEM0 , GEM1, GEM2)
*
*/
/*QM_reset -- reset QM context to initial default values */
static int QM_reset(PQM_context_ctl qm_context_ctl, int index) __attribute__ ((noinline));
static int QM_reset(PQM_context_ctl qm_context_ctl, int index)
{
int i;
if (index >= GEM_PORTS)
return CMD_ERR;
memset(qm_context_ctl, 0, sizeof(struct tQM_context_ctl));
qm_context_ctl->port = index;
qm_context_ctl->tmu_id = index + TMU0_ID;
/* Assign first 8 queues to scheduler 0
and other 8 queues to scheduler 1 */
for (i = 0; i < NUM_QUEUES; i++) {
if (i == 0)
qm_context_ctl->max_qdepth[i] = DEFAULT_Q0_QDEPTH;
else
qm_context_ctl->max_qdepth[i] = DEFAULT_MAX_QDEPTH;
qm_context_ctl->q[i].shaper_num = DEFAULT_SHAPER;
qm_context_ctl->weight[i] = 0;
if (i < 8)
qm_context_ctl->q[i].sched_num = 0;
else
qm_context_ctl->q[i].sched_num = 1;
}
QM_hwqueue_init(qm_context_ctl);
qm_context_ctl->ifg = QM_IFG_SIZE + 4; // add 4 to account for FCS
qm_context_ctl->num_sched = 2;
for (i = 0; i < NUM_SCHEDULERS; i++) {
qm_context_ctl->sched[i].alg = QM_ALG_PQ;
}
return NO_ERR;
}
/** Resets to the default configuration for exception path TMU.
* This function resets the QM control context to
* its default configuration.
* Matches tmu3_init
*/
static void QM_reset_tmu3(void)
{
int qno, i;
PQM_context_ctl qm_context_ctl;
qm_context_ctl = &gQMExptCtx;
memset(qm_context_ctl, 0, sizeof(struct tQM_context_ctl));
qm_context_ctl->port = EXPT_PORT_ID;
qm_context_ctl->tmu_id = TMU3_ID;
for (qno = 0; qno < NUM_QUEUES; qno++)
{
qm_context_ctl->max_qdepth[qno] = DEFAULT_TMU3_QDEPTH;
qm_context_ctl->q[qno].shaper_num = NO_SHAPER;
qm_context_ctl->weight[qno] = 0;
switch(qno)
{
case TMU_QUEUE_PCAP:
qm_context_ctl->q[qno].sched_num = 0;
break;
case TMU_QUEUE_IPSEC_IN:
case TMU_QUEUE_IPSEC_OUT:
case TMU_QUEUE_REASSEMBLY:
case TMU_QUEUE_LRO:
case TMU_QUEUE_EXPT_0:
case TMU_QUEUE_WIFI_0:
qm_context_ctl->q[qno].sched_num = 1;
break;
case TMU_QUEUE_EXPT_1:
case TMU_QUEUE_WIFI_1:
qm_context_ctl->q[qno].sched_num = 2;
break;
case TMU_QUEUE_EXPT_2:
case TMU_QUEUE_WIFI_2:
qm_context_ctl->q[qno].sched_num = 3;
break;
case TMU_QUEUE_EXPT_3:
case TMU_QUEUE_EXPT_ARP:
case TMU_QUEUE_WIFI_3:
qm_context_ctl->q[qno].sched_num = 4;
break;
case TMU_QUEUE_RTP_RELAY:
case TMU_QUEUE_RTP_CUTTHRU:
qm_context_ctl->q[qno].sched_num = 5;
break;
}
}
QM_hwqueue_init(qm_context_ctl);
qm_context_ctl->ifg = 0;
qm_context_ctl->num_sched = 6;
for (i = 0; i < qm_context_ctl->num_sched; i++) {
qm_context_ctl->sched[i].alg = QM_ALG_RR;
}
QM_update_scheduler(qm_context_ctl);
QM_update_qlenmask(qm_context_ctl);
#define ENABLE_TMU3_HWSHAPER /* comment out to disable TMU 3 HIF port shaper */
#ifdef ENABLE_TMU3_HWSHAPER
{
// set up port shaper for exception path
// this is a workaround for a hw bug that can cause BMU leakage on HIF packets (root cause not known yet)
#define TMU3_SHAPER_RATE 2000000 /* rate is Kbps */
PQM_ShaperDesc pshaper;
PQM_ShaperDesc_ctl pshaper_ctl;
pshaper = &qm_context_ctl->hw_shaper[PORT_SHAPER_INDEX];
pshaper->shaper_rate = TMU3_SHAPER_RATE;
pshaper_ctl = &qm_context_ctl->hw_shaper_ctl[PORT_SHAPER_INDEX];
pshaper_ctl->enable = TRUE;
pshaper_ctl->max_credit = 1;
qm_cal_shaperwts(TMU3_SHAPER_RATE, pshaper_ctl);
pshaper->qmask = PORT_SHAPER_QMASK_TMU3;
for (i = 4; i <= 14; i++)
qm_context_ctl->q_len_masks[i] |= (1 << (NUM_SCHEDULERS + PORT_SHAPER_INDEX));
qm_context_ctl->ifg = QM_IFG_SIZE;
QM_update_ifg(qm_context_ctl);
}
#endif
QM_update_TMU(qm_context_ctl, SHAPER_CONFIG);
}
/** DSCP to Queue mapping configuration.
* This function handles the dscp to queue mapping in the local structure
* and transfers the same structure to all classpe's
*
* @param p DSCP to queue command structure.
* @param Length length of the command passed.
*
*/
static int QM_Handle_DSCP_QueueMod(U16 *p, U16 Length)
{
QoSDSCPQmodCommand cmd;
int i, id;
struct pfe_ctrl *ctrl = &pfe->ctrl;
if (Length > sizeof(QoSDSCPQmodCommand))
return ERR_WRONG_COMMAND_SIZE;
SFL_memcpy((U8*)&cmd, (U8*)p, sizeof(QoSDSCPQmodCommand));
if(cmd.queue >= NUM_QUEUES)
return ERR_QM_QUEUE_OUT_OF_RANGE;
if(cmd.num_dscp > NUM_DSCP_VALUES)
return ERR_QM_NUM_DSCP_OUT_OF_RANGE;
for(i = 0; i < cmd.num_dscp; i++)
if(cmd.dscp[i] >= NUM_DSCP_VALUES)
return ERR_QM_DSCP_OUT_OF_RANGE;
//the whole command is correct, we can assign dscp to queues
for(i = 0; i < cmd.num_dscp; i++)
DSCP_to_Qmod[cmd.dscp[i]] = cmd.queue;
if( pe_sync_stop(ctrl, CLASS_MASK) < 0)
return CMD_ERR;
/* update the DMEM in class-pe */
for (id = CLASS0_ID; id <= CLASS_MAX_ID; id++)
pe_dmem_memcpy_to32(id, virt_to_class_dmem(DSCP_to_Qmod), DSCP_to_Qmod, sizeof (DSCP_to_Qmod));
pe_start(ctrl, CLASS_MASK);
return NO_ERR;
}
/** Fill the QOS query command.
* This function is used to fill the information from the qos context
* to the command strucutre on issue of query command.
*
* @param pQoscmd Qos query command structure.
*
*/
static void QM_Get_Info(pQosQueryCmd pQoscmd)
{
PQM_context_ctl qm_context_ctl;
PQM_ShaperDesc pshaper;
PQM_ShaperDesc_ctl pshaper_ctl;
PQM_SchedDesc psched;
//PQM_QDesc pq;
int i;
qm_context_ctl = QM_GET_CONTEXT(pQoscmd->port);
pQoscmd->queue_qosenable_mask = qm_global_stat[qm_context_ctl->port] == QM_ENABLE ? 0xffffffff : 0;
//pQoscmd->max_txdepth = 0; // not used for C2K
for (i = 0; i < NUM_SHAPERS; i++)
{
pshaper = &qm_context_ctl->hw_shaper[i];
pshaper_ctl = &qm_context_ctl->hw_shaper_ctl[i];
pQoscmd->shaper_qmask[i] = pshaper->qmask;
pQoscmd->shaper_rate[i] = pshaper_ctl->enable ? pshaper->shaper_rate : 0;
pQoscmd->bucket_size[i] = qm_context_ctl->bucket_size[i];
}
for (i = 0; i < NUM_SCHEDULERS; i++)
{
psched = &qm_context_ctl->sched[i];
pQoscmd->sched_qmask[i] = psched->qmask;
pQoscmd->sched_alg[i] = psched->alg;
}
for (i = 0; i < NUM_QUEUES; i++)
{
//pq = &qm_context->q[i];
pQoscmd->max_qdepth[i] = qm_context_ctl->max_qdepth[i];
//pQoscmd->weight[i] = pq->weight;
}
return;
}
/** QOS calculate weights and clock divider based on rate.
* This function calculates weights and clock divider value according to
* the rate configured.
*
* Note that the clock divider must be a power of two.
*
* @param rate Rate configured in Kbps.
* @param shaper_ctl shaper structure where shaper info needs to be configured.
*
*/
#define MAX_CLKDIV 8192
#define MAX_WT 1023
#define MAX_VALID_RATE 50000000 /* should never need 50 Gbps shaper */
int qm_cal_shaperwts(u32 rate, PQM_ShaperDesc_ctl shaper_ctl)
{
struct pfe_ctrl *ctrl = &pfe->ctrl;
u32 sysclk_khz = ctrl->sys_clk;
u32 clkdiv, trydiv;
u32 wt;
u32 w_i, w_f;
if (rate == 0 || rate > MAX_VALID_RATE)
{
printk(KERN_ERR "Invalid shaper rate requested (%d) -- must be 1 to %d\n", rate, MAX_VALID_RATE);
return -1;
}
#ifdef QM_DEBUG
printk("axi_clk = %d\n", sysclk_khz);
#endif
clkdiv = 1;
// find the clkdiv value that gives us the largest valid wt value
while (clkdiv < MAX_CLKDIV)
{
trydiv = clkdiv << 1;
wt = (rate * trydiv * (256 / 8)) / sysclk_khz;
if (wt > MAX_WT)
break;
clkdiv = trydiv;
}
wt = (rate * clkdiv * (256 / 8)) / sysclk_khz;
w_i = wt >> 8;
w_f = wt & 0xff;
if (CHIP_REVISION() == 0)
{
// Workaround for hardware bug
while (w_f >= 128 && clkdiv > 1)
{
clkdiv /= 2;
wt = (rate * clkdiv * (256 / 8)) / sysclk_khz;
w_i = wt >> 8;
w_f = wt & 0xFF;
}
}
shaper_ctl->clk_div = clkdiv;
shaper_ctl->int_wt = w_i;
shaper_ctl->frac_wt = w_f;
#ifdef QM_DEBUG
printk(KERN_INFO "shaper settings: rate=%d, clock_div=%d, int_wt=%d, frac_wt=%d \n", rate, shaper_ctl->clk_div, shaper_ctl->int_wt, shaper_ctl->frac_wt);
#endif
/*
* There will be a slight error due to w_i and w_f calculations.
* The actual rate will always be less than or equal to the desired rate.
*
* The error can be calculated as:
* float actual_rate, error;
* actual_rate = ((float)sysclk_khz / (float)clk_div) * ((float)w_i + ((float)w_f / 256.0)) * 8.0;
* error = ((float)rate - actual_rate) / (float)rate;
*/
return 0;
}
/** QM_update_expt_rate_limiting
* This function configures the control context
* for required rate limiting
*
* @param qm_context_ctl QM context
* @param expt_itf Exception path interface (Eth or Wifi)
* @param rate Packet rate in pkts/msec
*/
static void QM_update_expt_rate_limiting(PQM_context_ctl qm_context_ctl, unsigned short expt_itf, unsigned int rate)
{
int i;
unsigned short queue_map = 0;
qm_context_ctl->sw_shaper[expt_itf].shaper_rate = rate;
if (rate == 0)
queue_map = 0;
else if (expt_itf == EXPT_TYPE_ETH)
queue_map = TMU3_EXPT_ETH_QM;
else if (expt_itf == EXPT_TYPE_WIFI)
queue_map = TMU3_EXPT_WIFI_QM;
else if (expt_itf == EXPT_TYPE_ARP)
queue_map = TMU3_EXPT_ARP_QM;
else if (expt_itf == EXPT_TYPE_PCAP)
queue_map = TMU3_EXPT_PCAP_QM;
qm_context_ctl->sw_shaper[expt_itf].qmask = queue_map;
qm_context_ctl->sw_shaper_ctl[expt_itf].enable = rate != 0;
qm_context_ctl->num_sw_shapers = 0;
for (i = 0; i < NUM_SW_SHAPERS; i++) {
if (qm_context_ctl->sw_shaper[i].shaper_rate)
qm_context_ctl->num_sw_shapers = i + 1;
}
}
/** QOS command executer.
* This function is the QOS handler function / the entry point
* to process the qos commands
*
* @param cmd_code Command code.
* @param cmd_len Command length.
* @param p Command structure.
*
*/
static U16 M_qm_cmdproc(U16 cmd_code, U16 cmd_len, U16 *p)
{
struct pfe_ctrl *ctrl = &pfe->ctrl;
struct tQM_context_ctl *qm_context_ctl;
int i;
U16 rtncode = 0;
U16 retlen = 2;
rtncode = CMD_OK;
#ifdef QM_DEBUG
printk(KERN_INFO "%s: cmd_code=0x%x\n", __func__, cmd_code);
#endif
switch (cmd_code)
{
// enable/disable QOS processing
case CMD_QM_QOSALG:
case CMD_QM_MAX_TXDEPTH:
case CMD_QM_RATE_LIMIT:
case CMD_QM_QUEUE_QOSENABLE:
break;
case CMD_QM_QOSENABLE:
{
PQueueQosEnableCommand pcmd = (PQueueQosEnableCommand)p;
if (pcmd->port >= GEM_PORTS)
{
rtncode = CMD_ERR;
break;
}
qm_context_ctl = QM_GET_CONTEXT(pcmd->port);
#ifdef QM_DEBUG
printk(KERN_INFO "QOS %s for port %d\n", pcmd->enable_flag ? "enable" : "disable", pcmd->port);
#endif
if (qm_global_stat[pcmd->port] == pcmd->enable_flag)
break;
if (pe_sync_stop(ctrl, 1 << qm_context_ctl->tmu_id) < 0)
{
rtncode = CMD_ERR;
break;
}
QM_update_qos_enabled_status(qm_context_ctl->port, pcmd->enable_flag);
rtncode = QM_update_context(pcmd->port,
SCHEDULER_CONFIG|SHAPER_CONFIG|QDEPTH_CONFIG|IFG_CONFIG);
pe_start(ctrl, 1 << qm_context_ctl->tmu_id);
break;
}
case CMD_QM_MAX_QDEPTH:
{
PQosMaxqdepthCommand pcmd;
#ifdef QM_DEBUG
printk(KERN_INFO "MAX QDEPTH command received %d\n", cmd_code);
#endif
pcmd = (PQosMaxqdepthCommand)p;
if (pcmd->port >= GEM_PORTS)
{
rtncode = CMD_ERR;
break;
}
qm_context_ctl = QM_GET_CONTEXT(pcmd->port);
for (i = 0; i < NUM_QUEUES; i++) {
if (pcmd->maxqdepth[i] > 0)
qm_context_ctl->max_qdepth[i] = pcmd->maxqdepth[i];
}
if (pe_sync_stop(ctrl, 1 << qm_context_ctl->tmu_id) < 0)
{
rtncode = CMD_ERR;
break;
}
rtncode = QM_update_context(pcmd->port, QDEPTH_CONFIG);
pe_start(ctrl, 1 << qm_context_ctl->tmu_id);
break;
}
// set weight parameters
case CMD_QM_MAX_WEIGHT:
{
PQosWeightCommand pcmd = (PQosWeightCommand)p;
if (pcmd->port >= GEM_PORTS)
{
rtncode = CMD_ERR;
break;
}
qm_context_ctl = QM_GET_CONTEXT(pcmd->port);
for (i = 0; i < NUM_QUEUES; i++) {
if (pcmd->weight[i] > 0)
{
#ifdef QM_DEBUG
printk(KERN_INFO "Setting qweight: port=%d, queue=%d, weight=%d\n", pcmd->port, i, pcmd->weight[i]);
#endif
qm_context_ctl->weight[i] = pcmd->weight[i];
}
}
if (pe_sync_stop(ctrl, 1 << qm_context_ctl->tmu_id) < 0)
{
rtncode = CMD_ERR;
break;
}
rtncode = QM_update_context(pcmd->port, SCHEDULER_CONFIG);
pe_start(ctrl, 1 << qm_context_ctl->tmu_id);
break;
}
// set exception handler rate limit
case CMD_QM_EXPT_RATE:
{
PQosExptRateCommand pcmd = (PQosExptRateCommand)p;
#ifdef QM_DEBUG
printk(KERN_INFO "EXPT Rate command received %d\n", cmd_code);
#endif
if ((pcmd->expt_iftype != EXPT_TYPE_ETH) && (pcmd->expt_iftype != EXPT_TYPE_WIFI) && (pcmd->expt_iftype != EXPT_TYPE_ARP) && (pcmd->expt_iftype != EXPT_TYPE_PCAP))
{
rtncode = CMD_ERR;
break;
}
QM_update_expt_rate_limiting(&gQMExptCtx, pcmd->expt_iftype, pcmd->pkts_per_msec);
QM_update_qlenmask(&gQMExptCtx);
if (pe_sync_stop(ctrl, 1 << TMU3_ID) < 0) {
rtncode = CMD_ERR;
break;
}
rtncode = QM_update_TMU(&gQMExptCtx, 0);
pe_start(ctrl, 1 << TMU3_ID);
break;
}
case CMD_QM_QUERY:
{
pQosQueryCmd pcmd = (pQosQueryCmd)p;
#ifdef QM_DEBUG
printk(KERN_INFO "QUERY command received %d - cmdlen %d - size%d\n", cmd_code,cmd_len, sizeof(QosQueryCmd));
#endif
if (cmd_len != sizeof(QosQueryCmd))
{
rtncode = CMD_ERR;
break;
}
QM_Get_Info(pcmd);
retlen = sizeof(QosQueryCmd);
break;
}
case CMD_QM_QUERY_EXPT_RATE:
{
PQosExptRateCommand pcmd = (PQosExptRateCommand)p;
if ((pcmd->expt_iftype != EXPT_TYPE_ETH) && (pcmd->expt_iftype != EXPT_TYPE_WIFI) && (pcmd->expt_iftype != EXPT_TYPE_ARP) && (pcmd->expt_iftype != EXPT_TYPE_PCAP))
{
rtncode = CMD_ERR;
break;
}
pcmd->pkts_per_msec = gQMExptCtx.sw_shaper[pcmd->expt_iftype].shaper_rate;
retlen = sizeof(QosExptRateCommand);
break;
}
case CMD_QM_RESET:
{
PQosResetCommand pcmd = (PQosResetCommand)p;
if (pcmd->port >= GEM_PORTS)
{
rtncode = CMD_ERR;
break;
}
qm_context_ctl = QM_GET_CONTEXT(pcmd->port);
rtncode = QM_reset(qm_context_ctl, pcmd->port);
if (rtncode != NO_ERR)
break;
if (pe_sync_stop(ctrl, 1 << qm_context_ctl->tmu_id) < 0)
{
rtncode = CMD_ERR;
break;
}
QM_update_qos_enabled_status(qm_context_ctl->port, QM_INITIAL_ENABLE_STATE);
rtncode = QM_update_context(pcmd->port,
SCHEDULER_CONFIG|SHAPER_CONFIG|QDEPTH_CONFIG|IFG_CONFIG);
pe_start(ctrl, 1 << qm_context_ctl->tmu_id);
break;
}
case CMD_QM_SHAPER_CONFIG:
{
U32 shaper_num;
PQM_ShaperDesc pshaper;
PQM_ShaperDesc_ctl pshaper_ctl;
U32 qmask;
PQosShaperConfigCommand pcmd = (PQosShaperConfigCommand)p;
if (pcmd->port >= GEM_PORTS)
{
rtncode = CMD_ERR;
break;
}
qm_context_ctl = QM_GET_CONTEXT(pcmd->port);
shaper_num = pcmd->shaper_num;
if (shaper_num == PORT_SHAPER_NUM)
{
shaper_num = PORT_SHAPER_INDEX;
}
else
{
if (shaper_num >= NUM_SHAPERS)
{
rtncode = CMD_ERR;
break;
}
}
pshaper = &qm_context_ctl->hw_shaper[shaper_num];
pshaper_ctl = &qm_context_ctl->hw_shaper_ctl[shaper_num];
if (pcmd->enable_disable_control == 1)
pshaper_ctl->enable = TRUE;
else if (pcmd->enable_disable_control == 2)
pshaper_ctl->enable = FALSE;
if (pcmd->ifg_change_flag)
qm_context_ctl->ifg = pcmd->ifg + 4; // add 4 to account for FCS
if (pcmd->rate)
{
U32 bucket_size;
/*Tokens stored in bits per 1ms
clock period =
(rate * bps_PER_Kbps)/MILLISEC_PER_SEC
* bps_PER_Kbps = 1000 & MILLISEC_PER_SEC = 1000
* Hence tokens per 1ms clock period = rate */
pshaper->shaper_rate = pcmd->rate;
qm_cal_shaperwts(pcmd->rate, pshaper_ctl);
bucket_size = pcmd->bucket_size;
qm_context_ctl->bucket_size[shaper_num] = bucket_size;
if (bucket_size == 0)
bucket_size = pcmd->rate; // default bucket size is bytes per msec
pshaper_ctl->max_credit = bucket_size / 8; // convert units to bytes
}
if (shaper_num != PORT_SHAPER_INDEX)
{
qmask = pcmd->qmask;
while (qmask)
{
i = FFS(qmask);
qm_context_ctl->q[i].shaper_num = shaper_num;
qmask &= ~(1 << i);
}
}
if (pe_sync_stop(ctrl, 1 << qm_context_ctl->tmu_id) < 0)
{
rtncode = CMD_ERR;
break;
}
if (pcmd->ifg_change_flag)
rtncode = QM_update_context(pcmd->port, SHAPER_CONFIG | IFG_CONFIG);
else
rtncode = QM_update_context(pcmd->port, SHAPER_CONFIG);
pe_start(ctrl, 1 << qm_context_ctl->tmu_id);
break;
}
case CMD_QM_SCHEDULER_CONFIG:
{
U32 sched_num;
PQM_SchedDesc psched;
U32 qmask;
U32 numqueues;
PQosSchedulerConfigCommand pcmd = (PQosSchedulerConfigCommand)p;
if (pcmd->port >= GEM_PORTS)
{
rtncode = CMD_ERR;
break;
}
qm_context_ctl = QM_GET_CONTEXT(pcmd->port);
sched_num = pcmd->sched_num;
if (sched_num >= NUM_SCHEDULERS)
{
rtncode = CMD_ERR;
break;
}
psched = &qm_context_ctl->sched[sched_num];
qmask = pcmd->qmask | psched->qmask;
numqueues = 0;
while(qmask)
{
i = FFS(qmask);
numqueues++;
qmask &= ~(1 << i);
}
if (numqueues > MAX_SCHEDULER_QUEUES)
{
rtncode = CMD_ERR;
break;
}
if (pcmd->alg_change_flag)
psched->alg = pcmd->alg;
qmask = pcmd->qmask;
while (qmask)
{
i = FFS(qmask);
qm_context_ctl->q[i].sched_num = sched_num;
qmask &= ~(1 << i);
}
if (pe_sync_stop(ctrl, 1 << qm_context_ctl->tmu_id) < 0)
{
rtncode = CMD_ERR;
break;
}
rtncode = QM_update_context(pcmd->port, SCHEDULER_CONFIG);
pe_start(ctrl, 1 << qm_context_ctl->tmu_id);
break;
}
case CMD_QM_DSCP_QM:
{
rtncode = QM_Handle_DSCP_QueueMod(p, cmd_len);
break;
}
// unknown command code
default:
{
rtncode = CMD_ERR;
break;
}
}
*p = rtncode;
#ifdef QM_DEBUG
if (rtncode != 0)
printk(KERN_INFO "%s: Command error, rtncode=%d", __func__, (short)rtncode);
#endif
return retlen;
}
/** QOS init function.
* This function initializes the qos control context with default configuration
* and sends the same configuration to TMU.
*
*/
int qm_init()
{
int i;
struct pfe_ctrl *ctrl = &pfe->ctrl;
PQM_context_ctl qm_context_ctl;
set_cmd_handler(EVENT_QM,M_qm_cmdproc);
set_cmd_handler(EVENT_EXPT,M_expt_cmdproc);
pe_sync_stop(ctrl, TMU_MASK);
for (i = 0; i < GEM_PORTS; i++)
{
qm_context_ctl = &gQMCtx[i];
gQMpCtx[i] = qm_context_ctl;
/* init to default values */
QM_reset(QM_GET_QOSOFF_CONTEXT(i), i);
QM_reset(qm_context_ctl, i);
QM_update_qos_enabled_status(i, QM_INITIAL_ENABLE_STATE);
QM_update_context(i, SCHEDULER_CONFIG|SHAPER_CONFIG|QDEPTH_CONFIG|IFG_CONFIG);
}
#ifdef QM_DEBUG
printk (KERN_INFO "Writing the tail drop mechanism at addr : %x val :%d \n", (unsigned int) TMU_TEQ_DISABLE_DROPCHK, 0x5);
#endif
/* Enable the taildrop mechanism */
writel(0x5, (void*)TMU_TEQ_DISABLE_DROPCHK );
/* Initialization for Exception Path QOS */
QM_reset_tmu3();
#ifdef QM_DEBUG
printk (KERN_INFO "ifg = %x\n", readl((void*)TMU_TDQ_IIFG_CFG));
#endif
pe_start(ctrl, TMU_MASK);
return NO_ERR;
}
/** QOS exit function.
*/
void qm_exit(void)
{
}