sonic-buildimage/platform/broadcom/saibcm-modules/sdklt/linux/knet/ngknet_extra.c
vmittal-msft 04b9ce8e32
[BCMSAI] Update BCMSAI debian to 6.0.0.10 with 6.5.23 SDK, and opennsl module to 6.5.23 (#9046)
Manual verification on switch (TH3 device)
admin@str2-xxxxx-01:~$ bcmcmd bsv
bsv
BRCM SAI ver: [6.0.0.10], OCP SAI ver: [1.9.1], SDK ver: [sdk-6.5.23]
drivshell>

admin@str2-xxxxx-01:~$ bcmcmd version
version
Broadcom Command Monitor: Copyright (c) 1998-2021 Broadcom
Release: sdk-6.5.23 built 20211020 (Wed Oct 20 06:52:58 2021)
From root@fedbbfdbee81:/__w/2/s/output/x86-xgsall-deb/hsdk
Platform: X86
OS: Unix (Posix)
Chips:
BCM56640_A0,
BCM56850_A0,
BCM56340_A0,
BCM56960_A0, BCM56860_A0,

   BCM56970_A0, BCM56870_A0,
   BCM56980_A0, BCM56980_B0,
  
   BCM56370_A0, BCM56275_A0, BCM56770_A0,
Chips:
BCM56780_A0, BCM56782_A0, BCM56784_A0, BCM56785_A0,
BCM56786_A0, BCM56787_A0, BCM56788_A0, BCM56789_A0,
BCM56880_A0, BCM56880_B0, BCM56881_A0, BCM56881_B0,
BCM56883_A0, BCM56883_B0, BCM56990_A0, BCM56990_B0,
BCM56991_B0, BCM56992_B0, BCM56996_A0, BCM56996_B0,
BCM56997_A0, BCM56997_B0

Variant drivers:
BCM56780_A0_CNA_1_2_10, BCM56780_A0_DNA_2_7_6_0, BCM56880_A0_CNA_1_2_9, BCM56880_A0_DNA_4_9_5_0
PHYs: BCM5400, BCM54182, BCM54185, BCM54180,
BCM54140, BCM54192, BCM54195, BCM54190,
BCM54194, BCM54210, BCM54220, BCM54280,
BCM54282, BCM54240, BCM54285, BCM5428X,
BCM54290, BCM54292, BCM54294, BCM54295,
BCM54296, BCM56160-GPHY, BCM53540-GPHY, BCM56275-GPHY,
BCM8750, BCM8752, BCM8754, BCM84740,
BCM84164, BCM84758, BCM84780, BCM84784,
BCM84318, BCM84328, Sesto, BCM82780,
copper sfp

drivshell>
2021-10-28 00:12:32 -07:00

539 lines
14 KiB
C

/*! \file ngknet_extra.c
*
* Utility routines for NGKNET enhancement.
*
*/
/*
* $Copyright: Copyright 2018-2021 Broadcom. All rights reserved.
* The term 'Broadcom' refers to Broadcom Inc. and/or its subsidiaries.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* 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.
*
* A copy of the GNU General Public License version 2 (GPLv2) can
* be found in the LICENSES folder.$
*/
#include <linux/kconfig.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/unistd.h>
#include <asm/io.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/if.h>
#include <linux/if_vlan.h>
#include <linux/net_tstamp.h>
#include <linux/spinlock.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/vmalloc.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/bitops.h>
#include <linux/time.h>
#include <lkm/ngknet_dev.h>
#include <bcmcnet/bcmcnet_core.h>
#include "ngknet_main.h"
#include "ngknet_extra.h"
#include "ngknet_callback.h"
/*! Defalut Rx tick for Rx rate limit control. */
#define NGKNET_EXTRA_RATE_LIMIT_DEFAULT_RX_TICK 10
static struct ngknet_rl_ctrl rl_ctrl;
int
ngknet_filter_create(struct ngknet_dev *dev, ngknet_filter_t *filter)
{
struct filt_ctrl *fc = NULL;
struct list_head *list = NULL;
ngknet_filter_t *filt = NULL;
unsigned long flags;
int num, id, done = 0;
switch (filter->type) {
case NGKNET_FILTER_T_RX_PKT:
break;
default:
return SHR_E_UNAVAIL;
}
switch (filter->dest_type) {
case NGKNET_FILTER_DEST_T_NULL:
case NGKNET_FILTER_DEST_T_NETIF:
case NGKNET_FILTER_DEST_T_VNET:
case NGKNET_FILTER_DEST_T_CB:
break;
default:
return SHR_E_UNAVAIL;
}
spin_lock_irqsave(&dev->lock, flags);
num = (long)dev->fc[0];
for (id = 1; id < num + 1; id++) {
if (!dev->fc[id]) {
break;
}
}
if (id > NUM_FILTER_MAX) {
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_RESOURCE;
}
fc = kzalloc(sizeof(*fc), GFP_KERNEL);
if (!fc) {
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_MEMORY;
}
dev->fc[id] = fc;
num += id == (num + 1) ? 1 : 0;
dev->fc[0] = (void *)(long)num;
memcpy(&fc->filt, filter, sizeof(fc->filt));
fc->filt.id = id;
list_for_each(list, &dev->filt_list) {
filt = &((struct filt_ctrl *)list)->filt;
if (filt->flags & NGKNET_FILTER_F_MATCH_CHAN) {
if (!(fc->filt.flags & NGKNET_FILTER_F_MATCH_CHAN) ||
fc->filt.chan > filt->chan) {
continue;
}
if (fc->filt.chan < filt->chan ||
fc->filt.priority < filt->priority) {
list_add_tail(&fc->list, list);
done = 1;
break;
}
} else {
if (fc->filt.flags & NGKNET_FILTER_F_MATCH_CHAN ||
fc->filt.priority < filt->priority) {
list_add_tail(&fc->list, list);
done = 1;
break;
}
}
}
if (!done) {
list_add_tail(&fc->list, &dev->filt_list);
}
filter->id = fc->filt.id;
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_NONE;
}
int
ngknet_filter_destroy(struct ngknet_dev *dev, int id)
{
struct filt_ctrl *fc = NULL;
unsigned long flags;
int num;
if (id <= 0 || id > NUM_FILTER_MAX) {
return SHR_E_PARAM;
}
spin_lock_irqsave(&dev->lock, flags);
fc = (struct filt_ctrl *)dev->fc[id];
if (!fc) {
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_NOT_FOUND;
}
list_del(&fc->list);
kfree(fc);
dev->fc[id] = NULL;
num = (long)dev->fc[0];
while (num-- == id--) {
if (dev->fc[id]) {
dev->fc[0] = (void *)(long)num;
break;
}
}
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_NONE;
}
int
ngknet_filter_destroy_all(struct ngknet_dev *dev)
{
int id;
int rv;
for (id = 1; id <= NUM_FILTER_MAX; id++) {
rv = ngknet_filter_destroy(dev, id);
if (SHR_FAILURE(rv)) {
return rv;
}
}
return SHR_E_NONE;
}
int
ngknet_filter_get(struct ngknet_dev *dev, int id, ngknet_filter_t *filter)
{
struct filt_ctrl *fc = NULL;
unsigned long flags;
int num;
if (id <= 0 || id > NUM_FILTER_MAX) {
return SHR_E_PARAM;
}
spin_lock_irqsave(&dev->lock, flags);
fc = (struct filt_ctrl *)dev->fc[id];
if (!fc) {
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_NOT_FOUND;
}
memcpy(filter, &fc->filt, sizeof(*filter));
num = (long)dev->fc[0];
for (id++; id < num + 1; id++) {
if (dev->fc[id]) {
break;
}
}
filter->next = id == (num + 1) ? 0 : id;
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_NONE;
}
int
ngknet_filter_get_next(struct ngknet_dev *dev, ngknet_filter_t *filter)
{
int id;
int rv;
if (!filter->next) {
for (id = 1; id <= NUM_FILTER_MAX; id++) {
rv = ngknet_filter_get(dev, id, filter);
if (SHR_SUCCESS(rv)) {
return rv;
}
}
if (id > NUM_FILTER_MAX) {
return SHR_E_NOT_FOUND;
}
}
return ngknet_filter_get(dev, filter->next, filter);
}
int
ngknet_rx_pkt_filter(struct ngknet_dev *dev, struct sk_buff *skb, struct net_device **ndev,
struct net_device **mndev, struct sk_buff **mskb)
{
struct pkt_buf *pkb = (struct pkt_buf *)skb->data;
struct net_device *dest_ndev = NULL, *mirror_ndev = NULL;
struct sk_buff *mirror_skb = NULL;
struct ngknet_private *priv = NULL;
struct filt_ctrl *fc = NULL;
struct list_head *list = NULL;
ngknet_filter_t scratch, *filt = NULL, *filt_cb = NULL;
uint8_t *oob = &pkb->data, *data = NULL;
uint16_t tpid;
unsigned long flags;
int wsize;
int chan_id;
int rv, idx, match = 0, match_cb = 0;
rv = bcmcnet_pdma_dev_queue_to_chan(&dev->pdma_dev, pkb->pkh.queue_id,
PDMA_Q_RX, &chan_id);
if (SHR_FAILURE(rv)) {
return rv;
}
spin_lock_irqsave(&dev->lock, flags);
dest_ndev = dev->bdev[chan_id];
if (dest_ndev) {
skb->dev = dest_ndev;
priv = netdev_priv(dest_ndev);
priv->users++;
*ndev = dest_ndev;
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_NONE;
}
if (list_empty(&dev->filt_list)) {
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_NONE;
}
list_for_each(list, &dev->filt_list) {
fc = (struct filt_ctrl *)list;
filt = &fc->filt;
if (filt->flags & NGKNET_FILTER_F_ANY_DATA) {
match = 1;
break;
}
if (filt->flags & NGKNET_FILTER_F_MATCH_CHAN && filt->chan != chan_id) {
continue;
}
memcpy(&scratch.data.b[0],
&oob[filt->oob_data_offset], filt->oob_data_size);
memcpy(&scratch.data.b[filt->oob_data_size],
&pkb->data + pkb->pkh.meta_len + filt->pkt_data_offset,
filt->pkt_data_size);
wsize = NGKNET_BYTES2WORDS(filt->oob_data_size + filt->pkt_data_size);
for (idx = 0; idx < wsize; idx++) {
scratch.data.w[idx] &= filt->mask.w[idx];
if (scratch.data.w[idx] != filt->data.w[idx]) {
break;
}
}
if (idx == wsize) {
if (NGKNET_FILTER_DEST_T_CB == filt->dest_type) {
match_cb = 1;
filt_cb = filt;
continue;
}
match = 1;
break;
}
}
if (match) {
fc->hits++;
if (filt->dest_type == NGKNET_FILTER_DEST_T_CB) {
struct ngknet_callback_desc *cbd = NGKNET_SKB_CB(skb);
struct pkt_hdr *pkh = (struct pkt_hdr *)skb->data;
if (!dev->cbc->filter_cb) {
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_UNAVAIL;
}
cbd->dev_no = dev->dev_no;
cbd->dev_id = dev->pdma_dev.dev_id;
cbd->type_str = dev->type_str;
cbd->pmd = skb->data + PKT_HDR_SIZE;
cbd->pmd_len = pkh->meta_len;
cbd->pkt_len = pkh->data_len;
cbd->filt = filt;
skb = dev->cbc->filter_cb(skb, &filt);
if (!skb || !filt) {
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_UNAVAIL;
}
}
switch (filt->dest_type) {
case NGKNET_FILTER_DEST_T_NETIF:
if (filt->dest_id == 0) {
dest_ndev = dev->net_dev;
} else {
dest_ndev = dev->vdev[filt->dest_id];
}
if (dest_ndev) {
skb->dev = dest_ndev;
if (filt->dest_proto) {
pkb->pkh.attrs |= PDMA_RX_SET_PROTO;
skb->protocol = filt->dest_proto;
}
priv = netdev_priv(dest_ndev);
priv->users++;
}
break;
case NGKNET_FILTER_DEST_T_VNET:
pkb->pkh.attrs |= PDMA_RX_TO_VNET;
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_NO_HANDLER;
case NGKNET_FILTER_DEST_T_NULL:
default:
spin_unlock_irqrestore(&dev->lock, flags);
return SHR_E_UNAVAIL;
}
}
spin_unlock_irqrestore(&dev->lock, flags);
if (!dest_ndev) {
return SHR_E_NONE;
} else {
*ndev = dest_ndev;
}
if (filt->flags & NGKNET_FILTER_F_STRIP_TAG) {
pkb->pkh.attrs |= PDMA_RX_STRIP_TAG;
data = skb->data + PKT_HDR_SIZE + pkb->pkh.meta_len;
tpid = data[12] << 8 | data[13];
if (tpid == ETH_P_8021Q || tpid == ETH_P_8021AD) {
pkb->pkh.data_len -= VLAN_HLEN;
memmove(skb->data + VLAN_HLEN, skb->data,
PKT_HDR_SIZE + pkb->pkh.meta_len + 2 * ETH_ALEN);
skb_pull(skb, VLAN_HLEN);
}
}
if (dev->cbc->rx_cb) {
NGKNET_SKB_CB(skb)->filt = filt;
/* Add callback filter if matched */
if (priv) {
priv->filt_cb = match_cb? filt_cb : NULL;
}
}
if (filt->mirror_type == NGKNET_FILTER_DEST_T_NETIF) {
spin_lock_irqsave(&dev->lock, flags);
if (filt->mirror_id == 0) {
mirror_ndev = dev->net_dev;
} else {
mirror_ndev = dev->vdev[filt->mirror_id];
}
if (mirror_ndev) {
mirror_skb = pskb_copy(skb, GFP_ATOMIC);
if (mirror_skb) {
mirror_skb->dev = mirror_ndev;
if (filt->mirror_proto) {
pkb->pkh.attrs |= PDMA_RX_SET_PROTO;
mirror_skb->protocol = filt->mirror_proto;
}
if (dev->cbc->rx_cb) {
NGKNET_SKB_CB(mirror_skb)->filt = filt;
}
priv = netdev_priv(mirror_ndev);
priv->users++;
*mndev = mirror_ndev;
*mskb = mirror_skb;
}
}
spin_unlock_irqrestore(&dev->lock, flags);
}
return SHR_E_NONE;
}
static void
ngknet_rl_process(timer_context_t data)
{
struct ngknet_rl_ctrl *rc = timer_arg(rc, data, timer);
struct ngknet_dev *dev;
unsigned long flags;
int idx;
spin_lock_irqsave(&rc->lock, flags);
rc->rx_pkts = 0;
for (idx = 0; idx < NUM_PDMA_DEV_MAX; idx++) {
dev = &rc->devs[idx];
if (rc->dev_active[idx] && rc->dev_paused[idx]) {
bcmcnet_pdma_dev_rx_resume(&dev->pdma_dev);
rl_ctrl.dev_paused[dev->dev_no] = 0;
}
}
spin_unlock_irqrestore(&rc->lock, flags);
rc->timer.expires = jiffies + HZ / rc->rx_ticks;
add_timer(&rc->timer);
}
void
ngknet_rx_rate_limit_init(struct ngknet_dev *devs)
{
sal_memset(&rl_ctrl, 0, sizeof(rl_ctrl));
rl_ctrl.rx_ticks = NGKNET_EXTRA_RATE_LIMIT_DEFAULT_RX_TICK;
setup_timer(&rl_ctrl.timer, ngknet_rl_process, (timer_context_t)&rl_ctrl);
spin_lock_init(&rl_ctrl.lock);
rl_ctrl.devs = devs;
}
void
ngknet_rx_rate_limit_cleanup(void)
{
del_timer_sync(&rl_ctrl.timer);
}
int
ngknet_rx_rate_limit_started(void)
{
return rl_ctrl.started;
}
void
ngknet_rx_rate_limit_start(struct ngknet_dev *dev)
{
unsigned long flags;
spin_lock_irqsave(&rl_ctrl.lock, flags);
rl_ctrl.dev_active[dev->dev_no] = 1;
spin_unlock_irqrestore(&rl_ctrl.lock, flags);
if (!rl_ctrl.started) {
rl_ctrl.started = 1;
rl_ctrl.timer.expires = jiffies + HZ / rl_ctrl.rx_ticks;
add_timer(&rl_ctrl.timer);
}
}
void
ngknet_rx_rate_limit_stop(struct ngknet_dev *dev)
{
unsigned long flags;
spin_lock_irqsave(&rl_ctrl.lock, flags);
rl_ctrl.dev_active[dev->dev_no] = 0;
spin_unlock_irqrestore(&rl_ctrl.lock, flags);
}
void
ngknet_rx_rate_limit(struct ngknet_dev *dev, int limit)
{
unsigned long flags;
/* To support lower rate, we should use smaller tick (larger interval). */
if (limit < 1000) {
rl_ctrl.rx_ticks = (limit + 99) / 100;
} else {
rl_ctrl.rx_ticks = NGKNET_EXTRA_RATE_LIMIT_DEFAULT_RX_TICK;
}
spin_lock_irqsave(&rl_ctrl.lock, flags);
if ((++rl_ctrl.rx_pkts + rl_ctrl.rx_overruns > limit / rl_ctrl.rx_ticks) &&
!rl_ctrl.dev_paused[dev->dev_no] && rl_ctrl.dev_active[dev->dev_no]) {
rl_ctrl.dev_paused[dev->dev_no] = 1;
rl_ctrl.rx_overruns = 0;
bcmcnet_pdma_dev_rx_suspend(&dev->pdma_dev);
}
if (rl_ctrl.dev_paused[dev->dev_no]) {
rl_ctrl.rx_overruns++;
}
spin_unlock_irqrestore(&rl_ctrl.lock, flags);
}
void
ngknet_tx_queue_schedule(struct ngknet_dev *dev, struct sk_buff *skb, int *queue)
{
struct pkt_buf *pkb = (struct pkt_buf *)skb->data;
if (pkb->pkh.attrs & PDMA_TX_BIND_QUE) {
*queue = pkb->pkh.queue_id;
}
}