/* * 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: psample_cb.c $ * $Copyright: (c) 2019 Broadcom Corp. * All Rights Reserved.$ */ /* * Driver for call-back functions for Linux KNET driver. * * This code is used to integrate packet sampling KNET callback to * the psample infra for sending sampled pkts to userspace sflow * applications such as Host Sflow (https://github.com/sflow/host-sflow) * using genetlink interfaces. * * 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/ * make BUILD_KNET_CB=1 * */ #include #include #include #include #include #include #include #include #include "psample-cb.h" #define PSAMPLE_CB_DBG #ifdef PSAMPLE_CB_DBG extern int debug; #define PSAMPLE_CB_DBG_LVL_VERB (0x1) #define PSAMPLE_CB_DBG_LVL_PMD (0x2) #define PSAMPLE_CB_DBG_PRINT(...) if (debug & PSAMPLE_CB_DBG_LVL_VERB) { printk(__VA_ARGS__); } #define PSAMPLE_CB_PMD_PRINT(...) if (debug & PSAMPLE_CB_DBG_LVL_PMD) { printk(__VA_ARGS__); } #else #define PSAMPLE_CB_DBG_PRINT(...) #define PSAMPLE_CB_PMD_PRINT(...) #endif #define FCS_SZ 4 #define PSAMPLE_NLA_PADDING 4 #define PSAMPLE_PKT_HANDLED (1) #define PSAMPLE_RATE_DFLT 1 #define PSAMPLE_SIZE_DFLT 128 static int psample_size = PSAMPLE_SIZE_DFLT; MODULE_PARAM(psample_size, int, 0); MODULE_PARM_DESC(psample_size, "psample pkt size (default 128 bytes)"); #define PSAMPLE_QLEN_DFLT 1024 static int psample_qlen = PSAMPLE_QLEN_DFLT; MODULE_PARAM(psample_qlen, int, 0); MODULE_PARM_DESC(psample_qlen, "psample queue length (default 1024 buffers)"); /* driver proc entry root */ static struct proc_dir_entry *psample_proc_root = NULL; static struct proc_dir_entry *knet_cb_proc_root = NULL; /* psample general info */ typedef struct { struct list_head netif_list; int netif_count; struct net *netns; spinlock_t lock; int dcb_type; } psample_info_t; static psample_info_t g_psample_info = {0}; /* Maintain sampled pkt statistics */ typedef struct psample_stats_s { unsigned long pkts_f_psample_cb; unsigned long pkts_f_psample_mod; unsigned long pkts_f_handled; unsigned long pkts_f_pass_through; unsigned long pkts_f_dst_mc; unsigned long pkts_c_qlen_cur; unsigned long pkts_c_qlen_hi; unsigned long pkts_d_qlen_max; unsigned long pkts_d_no_mem; unsigned long pkts_d_no_group; unsigned long pkts_d_sampling_disabled; unsigned long pkts_d_not_ready; unsigned long pkts_d_metadata; unsigned long pkts_d_skb; unsigned long pkts_d_skb_cbd; unsigned long pkts_d_meta_srcport; unsigned long pkts_d_meta_dstport; unsigned long pkts_d_invalid_size; } psample_stats_t; static psample_stats_t g_psample_stats = {0}; typedef struct psample_meta_s { int trunc_size; int src_ifindex; int dst_ifindex; int sample_rate; } psample_meta_t; typedef struct psample_pkt_s { struct list_head list; struct psample_group *group; psample_meta_t meta; struct sk_buff *skb; } psample_pkt_t; typedef struct psample_work_s { struct list_head pkt_list; struct work_struct wq; spinlock_t lock; } psample_work_t; static psample_work_t g_psample_work = {0}; static psample_netif_t* psample_netif_lookup_by_ifindex(int ifindex) { struct list_head *list; psample_netif_t *psample_netif = NULL; unsigned long flags; /* look for port from list of available net_devices */ spin_lock_irqsave(&g_psample_info.lock, flags); list_for_each(list, &g_psample_info.netif_list) { psample_netif = (psample_netif_t*)list; if (psample_netif->dev->ifindex == ifindex) { spin_unlock_irqrestore(&g_psample_info.lock, flags); return psample_netif; } } spin_unlock_irqrestore(&g_psample_info.lock, flags); return (NULL); } static psample_netif_t* psample_netif_lookup_by_port(int port) { struct list_head *list; psample_netif_t *psample_netif = NULL; unsigned long flags; /* look for port from list of available net_devices */ spin_lock_irqsave(&g_psample_info.lock, flags); list_for_each(list, &g_psample_info.netif_list) { psample_netif = (psample_netif_t*)list; if (psample_netif->port == port) { spin_unlock_irqrestore(&g_psample_info.lock, flags); return psample_netif; } } spin_unlock_irqrestore(&g_psample_info.lock, flags); return (NULL); } static int psample_meta_sample_reason(uint8_t *pkt, void *pkt_meta) { uint32_t *metadata = (uint32_t*)pkt_meta; uint32_t reason = 0; uint32_t reason_hi = 0; uint32_t sample_rx_reason_mask = 0; if (metadata) { /* Sample Pkt reason code (bcmRxReasonSampleSource) */ switch(g_psample_info.dcb_type) { case 36: /* TD3 */ case 38: /* TH3 */ reason_hi = *(metadata + 4); reason = *(metadata + 5); sample_rx_reason_mask = (1 << 3); break; case 32: /* TH1/TH2 */ case 26: /* TD2 */ case 23: /* HX4 */ reason_hi = *(metadata + 2); reason = *(metadata + 3); sample_rx_reason_mask = (1 << 5); break; default: break; } } PSAMPLE_CB_DBG_PRINT("%s: DCB%d sample_rx_reason_mask: 0x%08x, reason: 0x%08x, reason_hi: 0x%08x\n", __func__, g_psample_info.dcb_type, sample_rx_reason_mask, reason, reason_hi); /* Check if only sample reason code is set. * If only sample reason code, then consume pkt. * If other reason codes exist, then pkt should be * passed through to Linux network stack. */ if ((reason & ~sample_rx_reason_mask) || reason_hi) { return 0; /* multiple reasons set, pass through */ } /* only sample rx reason set, consume pkt */ return (PSAMPLE_PKT_HANDLED); } static int psample_meta_get(struct sk_buff *skb, psample_meta_t *sflow_meta) { int src_ifindex = 0; int sample_rate = 1; int sample_size = PSAMPLE_SIZE_DFLT; psample_netif_t *psample_netif = NULL; const struct ngknet_callback_desc *cbd = NGKNET_SKB_CB(skb); const struct ngknet_private *netif = cbd->priv; memset(sflow_meta, 0, sizeof(psample_meta_t)); /* find src port */ if ((psample_netif = psample_netif_lookup_by_ifindex(netif->net_dev->ifindex))) { src_ifindex = psample_netif->dev->ifindex; sample_rate = psample_netif->sample_rate; sample_size = psample_netif->sample_size; } else { g_psample_stats.pkts_d_meta_srcport++; PSAMPLE_CB_DBG_PRINT("%s: could not find psample netif for src dev %s (ifidx %d)\n", __func__, netif->net_dev->name, netif->net_dev->ifindex); } sflow_meta->src_ifindex = src_ifindex; sflow_meta->trunc_size = sample_size; sflow_meta->sample_rate = sample_rate; return (0); } static void psample_task(struct work_struct *work) { psample_work_t *psample_work = container_of(work, psample_work_t, wq); unsigned long flags; struct list_head *list_ptr, *list_next; psample_pkt_t *pkt; spin_lock_irqsave(&psample_work->lock, flags); list_for_each_safe(list_ptr, list_next, &psample_work->pkt_list) { /* dequeue pkt from list */ pkt = list_entry(list_ptr, psample_pkt_t, list); list_del(list_ptr); g_psample_stats.pkts_c_qlen_cur--; spin_unlock_irqrestore(&psample_work->lock, flags); /* send to psample */ if (pkt) { PSAMPLE_CB_DBG_PRINT("%s: group 0x%x, trunc_size %d, src_ifdx 0x%x, dst_ifdx 0x%x, sample_rate %d\n", __func__, pkt->group->group_num, pkt->meta.trunc_size, pkt->meta.src_ifindex, pkt->meta.dst_ifindex, pkt->meta.sample_rate); psample_sample_packet(pkt->group, pkt->skb, pkt->meta.trunc_size, pkt->meta.src_ifindex, pkt->meta.dst_ifindex, pkt->meta.sample_rate); g_psample_stats.pkts_f_psample_mod++; dev_kfree_skb_any(pkt->skb); kfree(pkt); } spin_lock_irqsave(&psample_work->lock, flags); } spin_unlock_irqrestore(&psample_work->lock, flags); } struct sk_buff* psample_rx_cb(struct sk_buff *skb) { struct psample_group *group; psample_meta_t meta; int rv = 0, size; const struct ngknet_callback_desc *cbd = NULL; const struct ngknet_private *netif = NULL; const struct ngknet_filter_s *filt = NULL; const struct ngknet_filter_s *filt_src = NULL; if (!skb) { printk("%s: skb is NULL\n", __func__); g_psample_stats.pkts_d_skb++; return (NULL); } cbd = NGKNET_SKB_CB(skb); netif = cbd->priv; filt_src = cbd->filt; filt = cbd->filt_cb; if (!cbd || !netif || !filt_src) { printk("%s: cbd(0x%p) or priv(0x%p) or filter src(0x%p) is NULL\n", __func__, cbd, netif, filt_src); g_psample_stats.pkts_d_skb_cbd++; return (skb); } /* Enable PMD output in dmesg: "echo "debug=0x2" > /proc/bcm/knet-cb/psample/debug" * Use bshell cmd "pmddecode rxpmd ..." to decode pkt metadata */ if (debug & PSAMPLE_CB_DBG_LVL_PMD) { char str[128]; int i, len = cbd->pmd_len > 128? 128 : cbd->pmd_len; PSAMPLE_CB_PMD_PRINT("PMD (%d bytes): %s\n", cbd->pmd_len, skb->dev->name); for (i=0; ipmd+i))); if ((i & 0x1c) == 0x1c) { sprintf(&str[strlen(str)], "\n"); printk(str); continue; } } if ((i & 0x1f) != 0) { sprintf(&str[strlen(str)], "\n"); PSAMPLE_CB_PMD_PRINT(str); } } /* check if this packet is sampled packet (from sample filter) */ if (!filt || (NGKNET_FILTER_DEST_T_CB != filt->dest_type) || (strncmp(filt->desc, PSAMPLE_CB_NAME, NGKNET_FILTER_DESC_MAX) != 0)) { return (skb); } PSAMPLE_CB_DBG_PRINT("%s: src dev %s, pkt size %d, filt->dest_id %d\n", __func__, skb->dev->name, cbd->pkt_len, filt->dest_id); g_psample_stats.pkts_f_psample_cb++; /* get psample group info. psample genetlink group ID passed in filt->dest_id */ group = psample_group_get(g_psample_info.netns, filt->dest_id); if (!group) { printk("%s: Could not find psample genetlink group %d\n", __func__, filt->dest_id); g_psample_stats.pkts_d_no_group++; goto PSAMPLE_FILTER_CB_PKT_HANDLED; } /* get psample metadata */ rv = psample_meta_get(skb, &meta); if (rv < 0) { printk("%s: Could not parse pkt metadata\n", __func__); g_psample_stats.pkts_d_metadata++; goto PSAMPLE_FILTER_CB_PKT_HANDLED; } /* Adjust original pkt size to remove 4B FCS */ size = cbd->pkt_len; if (size < FCS_SZ) { g_psample_stats.pkts_d_invalid_size++; goto PSAMPLE_FILTER_CB_PKT_HANDLED; } else { size -= FCS_SZ; } /* Account for padding in libnl used by psample */ if (meta.trunc_size >= size) { meta.trunc_size = size - PSAMPLE_NLA_PADDING; } PSAMPLE_CB_DBG_PRINT("%s: group 0x%x, trunc_size %d, src_ifdx 0x%x, dst_ifdx 0x%x, sample_rate %d\n", __func__, group->group_num, meta.trunc_size, meta.src_ifindex, meta.dst_ifindex, meta.sample_rate); /* drop if configured sample rate is 0 */ if (meta.sample_rate > 0) { unsigned long flags; psample_pkt_t *psample_pkt; struct sk_buff *skb_psample; if (g_psample_stats.pkts_c_qlen_cur >= psample_qlen) { printk("%s: tail drop due to max qlen %d reached\n", __func__, psample_qlen); g_psample_stats.pkts_d_qlen_max++; goto PSAMPLE_FILTER_CB_PKT_HANDLED; } if ((psample_pkt = kmalloc(sizeof(psample_pkt_t), GFP_ATOMIC)) == NULL) { printk("%s: failed to alloc psample mem for pkt\n", __func__); g_psample_stats.pkts_d_no_mem++; goto PSAMPLE_FILTER_CB_PKT_HANDLED; } memcpy(&psample_pkt->meta, &meta, sizeof(psample_meta_t)); psample_pkt->group = group; if ((skb_psample = dev_alloc_skb(meta.trunc_size)) == NULL) { printk("%s: failed to alloc psample mem for pkt skb\n", __func__); g_psample_stats.pkts_d_no_mem++; goto PSAMPLE_FILTER_CB_PKT_HANDLED; } /* setup skb to point to pkt */ memcpy(skb_psample->data, skb->data, meta.trunc_size); skb_put(skb_psample, meta.trunc_size); skb_psample->len = meta.trunc_size; psample_pkt->skb = skb_psample; spin_lock_irqsave(&g_psample_work.lock, flags); list_add_tail(&psample_pkt->list, &g_psample_work.pkt_list); g_psample_stats.pkts_c_qlen_cur++; if (g_psample_stats.pkts_c_qlen_cur > g_psample_stats.pkts_c_qlen_hi) { g_psample_stats.pkts_c_qlen_hi = g_psample_stats.pkts_c_qlen_cur; } schedule_work(&g_psample_work.wq); spin_unlock_irqrestore(&g_psample_work.lock, flags); } else { g_psample_stats.pkts_d_sampling_disabled++; } PSAMPLE_FILTER_CB_PKT_HANDLED: /* if sample reason only, consume pkt. else pass through */ rv = psample_meta_sample_reason(skb->data, cbd->pmd); if (rv) { g_psample_stats.pkts_f_handled++; return NULL; } g_psample_stats.pkts_f_pass_through++; return skb; } int psample_netif_create_cb(struct net_device *dev) { int found; struct list_head *list; psample_netif_t *psample_netif, *lpsample_netif; unsigned long flags; struct ngknet_private *netif = NULL; if (!dev) { printk("%s: net_device is NULL\n", __func__); return (-1); } netif = netdev_priv(dev); if ((psample_netif = kmalloc(sizeof(psample_netif_t), GFP_ATOMIC)) == NULL) { printk("%s: failed to alloc psample mem for netif '%s'\n", __func__, dev->name); return (-1); } spin_lock_irqsave(&g_psample_info.lock, flags); psample_netif->dev = dev; psample_netif->id = netif->id; /* FIXME: port is not saved in ngknet_private, need to get from metadata?? */ //psample_netif->port = netif->port; psample_netif->vlan = netif->vlan; psample_netif->sample_rate = PSAMPLE_RATE_DFLT; psample_netif->sample_size = PSAMPLE_SIZE_DFLT; /* insert netif sorted by ID similar to bkn_knet_netif_create() */ found = 0; list_for_each(list, &g_psample_info.netif_list) { lpsample_netif = (psample_netif_t*)list; if (netif->id < lpsample_netif->id) { found = 1; g_psample_info.netif_count++; break; } } if (found) { /* Replace previously removed interface */ list_add_tail(&psample_netif->list, &lpsample_netif->list); } else { /* No holes - add to end of list */ list_add_tail(&psample_netif->list, &g_psample_info.netif_list); } spin_unlock_irqrestore(&g_psample_info.lock, flags); PSAMPLE_CB_DBG_PRINT("%s: added psample netif '%s'\n", __func__, dev->name); return (0); } int psample_netif_destroy_cb(struct net_device *dev) { int found; struct list_head *list; psample_netif_t *psample_netif; unsigned long flags; struct ngknet_private *netif = NULL; if (!dev) { printk("%s: net_device is NULL\n", __func__); return (-1); } netif = netdev_priv(dev); spin_lock_irqsave(&g_psample_info.lock, flags); list_for_each(list, &g_psample_info.netif_list) { psample_netif = (psample_netif_t*)list; if (netif->id == psample_netif->id) { found = 1; list_del(&psample_netif->list); PSAMPLE_CB_DBG_PRINT("%s: removing psample netif '%s'\n", __func__, dev->name); kfree(psample_netif); g_psample_info.netif_count--; break; } } spin_unlock_irqrestore(&g_psample_info.lock, flags); if (!found) { printk("%s: netif ID %d not found!\n", __func__, netif->id); return (-1); } return (0); } /* * psample rate Proc Read Entry */ static int psample_proc_rate_show(struct seq_file *m, void *v) { struct list_head *list; psample_netif_t *psample_netif; unsigned long flags; spin_lock_irqsave(&g_psample_info.lock, flags); list_for_each(list, &g_psample_info.netif_list) { psample_netif = (psample_netif_t*)list; seq_printf(m, " %-14s %d\n", psample_netif->dev->name, psample_netif->sample_rate); } spin_unlock_irqrestore(&g_psample_info.lock, flags); return 0; } static int psample_proc_rate_open(struct inode * inode, struct file * file) { return single_open(file, psample_proc_rate_show, NULL); } /* * psample rate Proc Write Entry * * Syntax: * = * * Where is a virtual network interface name. * * Examples: * eth4=1000 */ static ssize_t psample_proc_rate_write(struct file *file, const char *buf, size_t count, loff_t *loff) { int found; struct list_head *list; psample_netif_t *psample_netif; char sample_str[40], *ptr, *newline; unsigned long flags; if (count > sizeof(sample_str)) { count = sizeof(sample_str) - 1; sample_str[count] = '\0'; } if (copy_from_user(sample_str, buf, count)) { return -EFAULT; } sample_str[count] = 0; newline = strchr(sample_str, '\n'); if (newline) { /* Chop off the trailing newline */ *newline = '\0'; } if ((ptr = strchr(sample_str, '=')) == NULL && (ptr = strchr(sample_str, ':')) == NULL) { printk("Error: Pkt sample rate syntax not recognized: '%s'\n", sample_str); return count; } *ptr++ = 0; spin_lock_irqsave(&g_psample_info.lock, flags); found = 0; list_for_each(list, &g_psample_info.netif_list) { psample_netif = (psample_netif_t*)list; if (strcmp(psample_netif->dev->name, sample_str) == 0) { psample_netif->sample_rate = simple_strtol(ptr, NULL, 10); // TODO MLI@BRCM - check valid sample rate found = 1; break; } } spin_unlock_irqrestore(&g_psample_info.lock, flags); if (!found) { printk("Warning: Failed setting psample rate on unknown network interface: '%s'\n", sample_str); } return count; } struct file_operations psample_proc_rate_file_ops = { owner: THIS_MODULE, open: psample_proc_rate_open, read: seq_read, llseek: seq_lseek, write: psample_proc_rate_write, release: single_release, }; /* * psample size Proc Read Entry */ static int psample_proc_size_show(struct seq_file *m, void *v) { struct list_head *list; psample_netif_t *psample_netif; unsigned long flags; spin_lock_irqsave(&g_psample_info.lock, flags); list_for_each(list, &g_psample_info.netif_list) { psample_netif = (psample_netif_t*)list; seq_printf(m, " %-14s %d\n", psample_netif->dev->name, psample_netif->sample_size); } spin_unlock_irqrestore(&g_psample_info.lock, flags); return 0; } static int psample_proc_size_open(struct inode * inode, struct file * file) { return single_open(file, psample_proc_size_show, NULL); } /* * psample size Proc Write Entry * * Syntax: * = * * Where is a virtual network interface name. * * Examples: * eth4=128 */ static ssize_t psample_proc_size_write(struct file *file, const char *buf, size_t count, loff_t *loff) { int found; struct list_head *list; psample_netif_t *psample_netif; char sample_str[40], *ptr, *newline; unsigned long flags; if (count > sizeof(sample_str)) { count = sizeof(sample_str) - 1; sample_str[count] = '\0'; } if (copy_from_user(sample_str, buf, count)) { return -EFAULT; } sample_str[count] = 0; newline = strchr(sample_str, '\n'); if (newline) { /* Chop off the trailing newline */ *newline = '\0'; } if ((ptr = strchr(sample_str, '=')) == NULL && (ptr = strchr(sample_str, ':')) == NULL) { printk("Error: Pkt sample size syntax not recognized: '%s'\n", sample_str); return count; } *ptr++ = 0; spin_lock_irqsave(&g_psample_info.lock, flags); found = 0; list_for_each(list, &g_psample_info.netif_list) { psample_netif = (psample_netif_t*)list; if (strcmp(psample_netif->dev->name, sample_str) == 0) { psample_netif->sample_size = simple_strtol(ptr, NULL, 10); // TODO MLI@BRCM - check valid sample size found = 1; break; } } spin_unlock_irqrestore(&g_psample_info.lock, flags); if (!found) { printk("Warning: Failed setting psample size on unknown network interface: '%s'\n", sample_str); } return count; } struct file_operations psample_proc_size_file_ops = { owner: THIS_MODULE, open: psample_proc_size_open, read: seq_read, llseek: seq_lseek, write: psample_proc_size_write, release: single_release, }; /* * psample map Proc Read Entry */ static int psample_proc_map_show(struct seq_file *m, void *v) { struct list_head *list; psample_netif_t *psample_netif; unsigned long flags; seq_printf(m, " Interface logical port ifindex\n"); seq_printf(m, "------------- ------------ -------\n"); spin_lock_irqsave(&g_psample_info.lock, flags); list_for_each(list, &g_psample_info.netif_list) { psample_netif = (psample_netif_t*)list; seq_printf(m, " %-14s %-14d %d\n", psample_netif->dev->name, psample_netif->port, psample_netif->dev->ifindex); } spin_unlock_irqrestore(&g_psample_info.lock, flags); return 0; } static int psample_proc_map_open(struct inode * inode, struct file * file) { return single_open(file, psample_proc_map_show, NULL); } struct file_operations psample_proc_map_file_ops = { owner: THIS_MODULE, open: psample_proc_map_open, read: seq_read, llseek: seq_lseek, write: NULL, release: single_release, }; /* * psample debug Proc Read Entry */ static int psample_proc_debug_show(struct seq_file *m, void *v) { seq_printf(m, "BCM KNET %s Callback Config\n", PSAMPLE_CB_NAME); seq_printf(m, " debug: 0x%x\n", debug); seq_printf(m, " dcb_type: %d\n", g_psample_info.dcb_type); seq_printf(m, " netif_count: %d\n", g_psample_info.netif_count); seq_printf(m, " queue length: %d\n", psample_qlen); return 0; } static int psample_proc_debug_open(struct inode * inode, struct file * file) { return single_open(file, psample_proc_debug_show, NULL); } /* * psample debug Proc Write Entry * * Syntax: * debug= * * Where corresponds to the debug module parameter. * * Examples: * debug=0x1 */ static ssize_t psample_proc_debug_write(struct file *file, const char *buf, size_t count, loff_t *loff) { char debug_str[40]; char *ptr; if (count > sizeof(debug_str)) { count = sizeof(debug_str) - 1; debug_str[count] = '\0'; } if (copy_from_user(debug_str, buf, count)) { return -EFAULT; } if ((ptr = strstr(debug_str, "debug=")) != NULL) { ptr += 6; debug = simple_strtol(ptr, NULL, 0); } else { printk("Warning: unknown configuration setting\n"); } return count; } struct file_operations psample_proc_debug_file_ops = { owner: THIS_MODULE, open: psample_proc_debug_open, read: seq_read, llseek: seq_lseek, write: psample_proc_debug_write, release: single_release, }; static int psample_proc_stats_show(struct seq_file *m, void *v) { seq_printf(m, "BCM KNET %s Callback Stats\n", PSAMPLE_CB_NAME); seq_printf(m, " DCB type %d\n", g_psample_info.dcb_type); seq_printf(m, " pkts filter psample cb %10lu\n", g_psample_stats.pkts_f_psample_cb); seq_printf(m, " pkts sent to psample module %10lu\n", g_psample_stats.pkts_f_psample_mod); seq_printf(m, " pkts handled by psample %10lu\n", g_psample_stats.pkts_f_handled); seq_printf(m, " pkts pass through %10lu\n", g_psample_stats.pkts_f_pass_through); seq_printf(m, " pkts with mc destination %10lu\n", g_psample_stats.pkts_f_dst_mc); seq_printf(m, " pkts current queue length %10lu\n", g_psample_stats.pkts_c_qlen_cur); seq_printf(m, " pkts high queue length %10lu\n", g_psample_stats.pkts_c_qlen_hi); seq_printf(m, " pkts drop max queue length %10lu\n", g_psample_stats.pkts_d_qlen_max); seq_printf(m, " pkts drop no memory %10lu\n", g_psample_stats.pkts_d_no_mem); seq_printf(m, " pkts drop no psample group %10lu\n", g_psample_stats.pkts_d_no_group); seq_printf(m, " pkts drop sampling disabled %10lu\n", g_psample_stats.pkts_d_sampling_disabled); seq_printf(m, " pkts drop psample not ready %10lu\n", g_psample_stats.pkts_d_not_ready); seq_printf(m, " pkts drop metadata parse error %10lu\n", g_psample_stats.pkts_d_metadata); seq_printf(m, " pkts drop skb error %10lu\n", g_psample_stats.pkts_d_skb); seq_printf(m, " pkts drop skb cbd error %10lu\n", g_psample_stats.pkts_d_skb_cbd); seq_printf(m, " pkts with invalid src port %10lu\n", g_psample_stats.pkts_d_meta_srcport); seq_printf(m, " pkts with invalid dst port %10lu\n", g_psample_stats.pkts_d_meta_dstport); seq_printf(m, " pkts with invalid orig pkt sz %10lu\n", g_psample_stats.pkts_d_invalid_size); return 0; } static int psample_proc_stats_open(struct inode * inode, struct file * file) { return single_open(file, psample_proc_stats_show, NULL); } /* * psample stats Proc Write Entry * * Syntax: * write any value to clear stats */ static ssize_t psample_proc_stats_write(struct file *file, const char *buf, size_t count, loff_t *loff) { int qlen_cur = 0; unsigned long flags; spin_lock_irqsave(&g_psample_work.lock, flags); qlen_cur = g_psample_stats.pkts_c_qlen_cur; memset(&g_psample_stats, 0, sizeof(psample_stats_t)); g_psample_stats.pkts_c_qlen_cur = qlen_cur; spin_unlock_irqrestore(&g_psample_work.lock, flags); return count; } struct file_operations psample_proc_stats_file_ops = { owner: THIS_MODULE, open: psample_proc_stats_open, read: seq_read, llseek: seq_lseek, write: psample_proc_stats_write, release: single_release, }; int psample_cleanup(void) { cancel_work_sync(&g_psample_work.wq); remove_proc_entry("stats", psample_proc_root); remove_proc_entry("rate", psample_proc_root); remove_proc_entry("size", psample_proc_root); remove_proc_entry("debug", psample_proc_root); remove_proc_entry("map" , psample_proc_root); remove_proc_entry("psample", knet_cb_proc_root); remove_proc_entry("bcm/knet-cb", NULL); remove_proc_entry("bcm", NULL); return 0; } int psample_init(void) { #define PROCFS_MAX_PATH 1024 char psample_procfs_path[PROCFS_MAX_PATH]; struct proc_dir_entry *entry; /* initialize proc files (for ngknet) */ proc_mkdir("bcm", NULL); /* create procfs for psample */ snprintf(psample_procfs_path, PROCFS_MAX_PATH, "bcm/knet-cb"); knet_cb_proc_root = proc_mkdir(psample_procfs_path, NULL); snprintf(psample_procfs_path, PROCFS_MAX_PATH, "%s/%s", psample_procfs_path, PSAMPLE_CB_NAME); psample_proc_root = proc_mkdir(psample_procfs_path, NULL); /* create procfs for psample stats */ PROC_CREATE(entry, "stats", 0666, psample_proc_root, &psample_proc_stats_file_ops); if (entry == NULL) { printk("%s: Unable to create procfs entry '/procfs/%s/stats'\n", __func__, psample_procfs_path); return -1; } /* create procfs for setting sample rates */ PROC_CREATE(entry, "rate", 0666, psample_proc_root, &psample_proc_rate_file_ops); if (entry == NULL) { printk("%s: Unable to create procfs entry '/procfs/%s/rate'\n", __func__, psample_procfs_path); return -1; } /* create procfs for setting sample size */ PROC_CREATE(entry, "size", 0666, psample_proc_root, &psample_proc_size_file_ops); if (entry == NULL) { printk("%s: Unable to create procfs entry '/procfs/%s/size'\n", __func__, psample_procfs_path); return -1; } /* create procfs for getting netdev mapping */ PROC_CREATE(entry, "map", 0666, psample_proc_root, &psample_proc_map_file_ops); if (entry == NULL) { printk("%s: Unable to create procfs entry '/procfs/%s/map'\n", __func__, psample_procfs_path); return -1; } /* create procfs for debug log */ PROC_CREATE(entry, "debug", 0666, psample_proc_root, &psample_proc_debug_file_ops); if (entry == NULL) { printk("%s: Unable to create procfs entry '/procfs/%s/debug'\n", __func__, psample_procfs_path); return -1; } /* clear data structs */ memset(&g_psample_stats, 0, sizeof(psample_stats_t)); memset(&g_psample_info, 0, sizeof(psample_info_t)); memset(&g_psample_work, 0, sizeof(psample_work_t)); /* FIXME: How to get DCB type from NGKNET? */ //g_psample_info.dcb_type /* setup psample_info struct */ INIT_LIST_HEAD(&g_psample_info.netif_list); spin_lock_init(&g_psample_info.lock); /* setup psample work queue */ spin_lock_init(&g_psample_work.lock); INIT_LIST_HEAD(&g_psample_work.pkt_list); INIT_WORK(&g_psample_work.wq, psample_task); /* get net namespace */ g_psample_info.netns = get_net_ns_by_pid(current->pid); if (!g_psample_info.netns) { printk("%s: Could not get network namespace for pid %d\n", __func__, current->pid); return (-1); } PSAMPLE_CB_DBG_PRINT("%s: current->pid %d, netns 0x%p, sample_size %d\n", __func__, current->pid, g_psample_info.netns, psample_size); return 0; }