platform/broadcom/saibcm-modules/systems/linux/kernel/modules/knet-cb/knet-cb.c (324 lines of code) (raw):

/* * Copyright 2017-2019 Broadcom * * 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 (the "GPL"). * * 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 version 2 (GPLv2) for more details. * * You should have received a copy of the GNU General Public License * version 2 (GPLv2) along with this source code. */ /* * $Id: $ * $Copyright: (c) 2017 Broadcom Corp. * All Rights Reserved.$ */ /* * Driver for call-back functions for Linux KNET driver. * * This is sample code that demonstrates how to selectively strip VLAN tags * from an incoming packet based on tag information in the DMA control block * (DCB). The switch will automatically add a VLAN tag to packets that ingress * without an outer VLAN tag. Outer tagged and double tagged packets are * not modified. The call back defined here determines which packets have * had tags added by those and strips only those tags from the packet. * * This is sample code, the customer is responsible for maintaining and * modifying this code as necessary. * * The module can be built from the standard Linux user mode target * directories using the following command (assuming bash), e.g. * * cd $SDK/systems/linux/user/<target> * make BUILD_KNET_CB=1 * */ #include <gmodule.h> /* Must be included first */ #include <kcom.h> #include <bcm-knet.h> #include <linux/if_vlan.h> /* Enable sflow sampling using psample */ #if IS_ENABLED(CONFIG_PSAMPLE) #include "psample-cb.h" #endif MODULE_AUTHOR("Broadcom Corporation"); MODULE_DESCRIPTION("Broadcom Linux KNET Call-Back Driver"); MODULE_LICENSE("GPL"); int debug; LKM_MOD_PARAM(debug, "i", int, 0); MODULE_PARM_DESC(debug, "Debug level (default 0)"); static int tpid=0x8100; LKM_MOD_PARAM(tpid, "i", int, 0); MODULE_PARM_DESC(debug, "Tag Protocol Identifier (TPID) indicates the frame type (default 0x8100)"); static int pri=0; LKM_MOD_PARAM(pri, "i", int, 0); MODULE_PARM_DESC(pri, "Priority (PRI) indicates the frame priority (default 0)"); static int cfi=0; LKM_MOD_PARAM(cfi, "i", int, 0); MODULE_PARM_DESC(cfi, "Canonical Format Indicator (CFI) indicates whether a MAC address is encapsulated in canonical format over different transmission media (default 0)"); static int vid=0; LKM_MOD_PARAM(vid, "i", int, 0); MODULE_PARM_DESC(vid, "VLAN ID (VID) indicates the VLAN to which a frame belongs (default 0)"); /* Module Information */ #define MODULE_MAJOR 121 #define MODULE_NAME "linux-knet-cb" /* set KNET_CB_DEBUG for debug info */ #define KNET_CB_DEBUG /* These below need to match incoming enum values */ #define FILTER_TAG_STRIP 0 #define FILTER_TAG_KEEP 1 #define FILTER_TAG_ORIGINAL 2 /* Maintain tag strip statistics */ struct strip_stats_s { unsigned long stripped; /* Number of packets that have been stripped */ unsigned long checked; unsigned long skipped; }; static struct strip_stats_s strip_stats; /* Local function prototypes */ static void strip_vlan_tag(struct sk_buff *skb); static int get_tag_status(int dcb_type, void *meta); static struct sk_buff *strip_tag_rx_cb(struct sk_buff *skb, int dev_no, void *meta); static struct sk_buff *strip_tag_tx_cb(struct sk_buff *skb, int dev_no, void *meta); static int strip_tag_filter_cb(uint8_t * pkt, int size, int dev_no, void *meta, int chan, kcom_filter_t * kf); static int _pprint(struct seq_file *m); static int _cleanup(void); static int _init(void); /* Remove VLAN tag for select TPIDs */ static void strip_vlan_tag(struct sk_buff *skb) { uint16_t vlan_proto = (uint16_t) ((skb->data[12] << 8) | skb->data[13]); if ((vlan_proto == 0x8100) || (vlan_proto == 0x88a8) || (vlan_proto == 0x9100)) { /* Move first 12 bytes of packet back by 4 */ ((u32 *) skb->data)[3] = ((u32 *) skb->data)[2]; ((u32 *) skb->data)[2] = ((u32 *) skb->data)[1]; ((u32 *) skb->data)[1] = ((u32 *) skb->data)[0]; skb_pull(skb, 4); /* Remove 4 bytes from start of buffer */ } } /* Add VLAN tag to untagged packet */ static void add_vlan_tag(struct sk_buff *skb, u32 forward_domain) { u32 vlan = 0; uint16_t vlan_proto = (uint16_t) ((skb->data[12] << 8) | skb->data[13]); if ((vlan_proto != 0x8100) && (vlan_proto != 0x88a8) && (vlan_proto != 0x9100)) { /* If vid is specified, use configued vid as VLAN ID, or, use forward_domain as vid */ vlan = vid ? vid: forward_domain; skb_push(skb, 4); /* Add 4 bytes from start of buffer */ /* Move first 12 bytes of packet forward by 4 */ ((u32 *) skb->data)[0] = ((u32 *) skb->data)[1]; ((u32 *) skb->data)[1] = ((u32 *) skb->data)[2]; ((u32 *) skb->data)[2] = ((u32 *) skb->data)[3]; /* Set VLAN tag */ skb->data[12] = (tpid >> 8) & 0xff; skb->data[13] = tpid & 0xff; skb->data[14] = (((pri & 0x7) << 5) | ((cfi & 0x1) << 4) | ((vlan >> 8) & 0xf)) & 0xff; skb->data[15] = vlan & 0xff; } } /* * Location of tagging status in select DCB types found below: * * DCB type 14: word 12, bits 10.11 * DCB type 19, 20, 21, 22, 30: word 12, bits 10..11 * DCB type 23, 29: word 13, bits 0..1 * DCB type 31, 34, 37: word 13, bits 0..1 * DCB type 26, 32, 33, 35: word 13, bits 0..1 * * The function get_tag_status() returns the tag status for known DCB types. * 0 = Untagged * 1 = Single inner-tag * 2 = Single outer-tag * 3 = Double tagged. * 4 = Dedicated for Dune device, packets are received with original tag status. * -1 = Unsupported DCB type */ static int get_tag_status(int dcb_type, void *meta) { uint32 *dcb = (uint32 *) meta; int tag_status; switch (dcb_type) { case 14: case 19: case 20: case 21: case 22: case 30: tag_status = (dcb[12] > 10) & 0x3; break; case 23: case 29: case 31: case 34: case 37: case 26: case 32: case 33: case 35: tag_status = dcb[13] & 0x3; break; case 36: /* TD3 */ tag_status = ((dcb[13] >> 9) & 0x3); break; break; case 38: { /* untested */ /* TH3 only parses outer tag. */ const int tag_map[4] = { 0, 2, -1, -1 }; tag_status = tag_map[(dcb[9] >> 13) & 0x3]; } break; case 28: case 39: tag_status = 4; break; break; default: tag_status = -1; break; } #ifdef KNET_CB_DEBUG if (debug & 0x1) { gprintk("%s; DCB Type: %d; tag status: %d\n", __func__, dcb_type, tag_status); } #endif return tag_status; } /* Rx packet callback function */ static struct sk_buff * strip_tag_rx_cb(struct sk_buff *skb, int dev_no, void *meta) { unsigned netif_flags = KNET_SKB_CB(skb)->netif_user_data; unsigned filter_flags = KNET_SKB_CB(skb)->filter_user_data; unsigned dcb_type; int tag_status; unsigned int strip_tag = 0; /* Currently not using filter flags: * unsigned filter_flags = KNET_SKB_CB(skb)->filter_user_data; */ #ifdef KNET_CB_DEBUG if (debug & 0x1) { gprintk("%s Enter; netif Flags: %08X filter_flags %08X \n", __func__, netif_flags, filter_flags); } #endif /* Get DCB type for this packet, passed by KNET driver */ dcb_type = KNET_SKB_CB(skb)->dcb_type; /* KNET implements this already */ if (filter_flags == FILTER_TAG_KEEP) { if (dcb_type ==28 || dcb_type == 39) { uint32 *meta_buffer = (uint32 *)meta; uint32 forward_domain = meta_buffer[1] & 0xffff; add_vlan_tag(skb, forward_domain); } strip_stats.skipped++; return skb; } /* SAI strip implies always strip. If the packet is untagged or inner taged, SDK adds a .1q tag, so we need to strip tag anyway */ if (filter_flags == FILTER_TAG_STRIP) { strip_tag = 1; } /* Get tag status from DCB */ tag_status = get_tag_status(dcb_type, meta); #ifdef KNET_CB_DEBUG if (debug & 0x1) { gprintk("%s; DCB Type: %d; tag status: %d\n", __func__, dcb_type, tag_status); } #endif if (tag_status < 0) { /* Unsupported DCB type */ return skb; } if (filter_flags == FILTER_TAG_ORIGINAL) { /* If untagged or single inner, strip the extra tag that knet keep tag will add. */ if (tag_status < 2) { strip_tag = 1; } } strip_stats.checked++; if (strip_tag) { #ifdef KNET_CB_DEBUG if (debug & 0x1) { gprintk("%s; Stripping VLAN tag\n", __func__); } #endif strip_stats.stripped++; strip_vlan_tag(skb); } #ifdef KNET_CB_DEBUG else { if (debug & 0x1) { gprintk("%s; Keeping VLAN tag\n", __func__); } } #endif return skb; } /* Tx callback not used */ static struct sk_buff * strip_tag_tx_cb(struct sk_buff *skb, int dev_no, void *meta) { /* Pass through for now */ return skb; } /* Filter callback not used */ static int strip_tag_filter_cb(uint8_t * pkt, int size, int dev_no, void *meta, int chan, kcom_filter_t *kf) { /* Pass through for now */ return 0; } #ifdef BCM_DNX_SUPPORT static int knet_filter_cb(uint8_t * pkt, int size, int dev_no, void *meta, int chan, kcom_filter_t *kf) { /* check for filter callback handler */ #if IS_ENABLED(CONFIG_PSAMPLE) if (strncmp(kf->desc, PSAMPLE_CB_NAME, strlen(PSAMPLE_CB_NAME)) == 0) { return psample_filter_cb (pkt, size, dev_no, meta, chan, kf); } #endif return strip_tag_filter_cb (pkt, size, dev_no, meta, chan, kf); } static int knet_netif_create_cb(int unit, kcom_netif_t *netif, uint16 spa, struct net_device *dev) { int retv = 0; #if IS_ENABLED(CONFIG_PSAMPLE) retv = psample_netif_create_cb(unit, netif, spa, dev); #endif return retv; } static int knet_netif_destroy_cb(int unit, kcom_netif_t *netif, uint16 spa, struct net_device *dev) { int retv = 0; #if IS_ENABLED(CONFIG_PSAMPLE) retv = psample_netif_destroy_cb(unit, netif, spa, dev); #endif return retv; } #else static int knet_filter_cb(uint8_t * pkt, int size, int dev_no, void *meta, int chan, kcom_filter_t *kf) { /* check for filter callback handler */ #if IS_ENABLED(CONFIG_PSAMPLE) if (strncmp(kf->desc, PSAMPLE_CB_NAME, KCOM_FILTER_DESC_MAX) == 0) { return psample_filter_cb (pkt, size, dev_no, meta, chan, kf); } #endif return strip_tag_filter_cb (pkt, size, dev_no, meta, chan, kf); } static int knet_netif_create_cb(int unit, kcom_netif_t *netif, struct net_device *dev) { int retv = 0; #if IS_ENABLED(CONFIG_PSAMPLE) retv = psample_netif_create_cb(unit, netif, dev); #endif return retv; } static int knet_netif_destroy_cb(int unit, kcom_netif_t *netif, struct net_device *dev) { int retv = 0; #if IS_ENABLED(CONFIG_PSAMPLE) retv = psample_netif_destroy_cb(unit, netif, dev); #endif return retv; } #endif /* * Get statistics. * % cat /proc/linux-knet-cb */ static int _pprint(struct seq_file *m) { pprintf(m, "Broadcom Linux KNET Call-Back: Untagged VLAN Stripper\n"); pprintf(m, " %lu stripped packets\n", strip_stats.stripped); pprintf(m ," %lu packets checked\n", strip_stats.checked); pprintf(m, " %lu packets skipped\n", strip_stats.skipped); return 0; } static int _cleanup(void) { bkn_rx_skb_cb_unregister(strip_tag_rx_cb); /* strip_tag_tx_cb is currently a noop, so * no need to unregister. */ if (0) { bkn_tx_skb_cb_unregister(strip_tag_tx_cb); } bkn_filter_cb_unregister(knet_filter_cb); bkn_netif_create_cb_unregister(knet_netif_create_cb); bkn_netif_destroy_cb_unregister(knet_netif_destroy_cb); #if IS_ENABLED(CONFIG_PSAMPLE) psample_cleanup(); #endif return 0; } static int _init(void) { bkn_rx_skb_cb_register(strip_tag_rx_cb); /* strip_tag_tx_cb is currently a noop, so * no need to register. */ if (0) { bkn_tx_skb_cb_register(strip_tag_tx_cb); } #if IS_ENABLED(CONFIG_PSAMPLE) psample_init(); #endif bkn_filter_cb_register(knet_filter_cb); bkn_netif_create_cb_register(knet_netif_create_cb); bkn_netif_destroy_cb_register(knet_netif_destroy_cb); return 0; } static gmodule_t _gmodule = { name: MODULE_NAME, major: MODULE_MAJOR, init: _init, cleanup: _cleanup, pprint: _pprint, ioctl: NULL, open: NULL, close: NULL, }; gmodule_t* gmodule_get(void) { EXPORT_NO_SYMBOLS; return &_gmodule; }