/* <:copyright-BRCM:2018:DUAL/GPL:standard Copyright (c) 2018 Broadcom All Rights Reserved 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 for more details. A copy of the GPL is available at http://www.broadcom.com/licenses/GPLv2.php, or by writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. :> */ #include #include #include #include #include "bdmf_system.h" #include "bdmf_sysb_chain.h" #include "wfd_dev_priv.h" #include "rate_limiter.h" static struct proc_dir_entry *rl_file_conf; typedef struct _rate_limit_t { uint32_t rate; uint32_t burst; uint32_t last_ts; uint32_t tokens; uint32_t drops; } rate_limit_t; /* Rate limiter for downstream and upstream directions of every interface for every radio */ static rate_limit_t dev_rate_limit[WFD_MAX_OBJECTS][WIFI_MW_MAX_NUM_IF][2] = {}; #define PROC_CMD_MAX_LEN 64 #define KBITS_TO_BYTES(mbits) (mbits*125) #define BYTES_TO_KBITS(bytes) (bytes/125) #define MBITS_TO_BYTES(mbits) (mbits*125000) #define BYTES_TO_MBITS(bytes) (bytes/125000) static uint32_t dbg_trace_rate = 0; static uint32_t dbg_trace_count = 0; #define rl_trace(fmt, params...) do { if (dbg_trace_rate && !--dbg_trace_count) \ { printk(fmt, params); dbg_trace_count = dbg_trace_rate; } \ } while (0) static ssize_t rate_limit_get_proc(struct file *file, char *buff, size_t len, loff_t *offset) { uint32_t radio_id; if (*offset) return 0; for (radio_id = 0; radio_id < WFD_MAX_OBJECTS; radio_id++) { uint32_t if_id; if (!wfd_is_radio_valid(radio_id)) continue; for (if_id = 0; if_id < WIFI_MW_MAX_NUM_IF; if_id++) { uint32_t dir; struct net_device *dev; if (!(dev = wfd_dev_by_id_get(radio_id, if_id))) continue; for (dir = 0; dir <= 1; dir++) { if (!dev_rate_limit[radio_id][if_id][dir].rate) continue; *offset += sprintf(buff + *offset, "%s [%s]:\n\trate: %u Mbps, burst: %u Mbps\n\tdrops: %u\n", dev->name, dir == RL_DIR_RX ? "RX" : "TX", BYTES_TO_MBITS(dev_rate_limit[radio_id][if_id][dir].rate), BYTES_TO_MBITS(dev_rate_limit[radio_id][if_id][dir].burst), dev_rate_limit[radio_id][if_id][dir].drops); dev_rate_limit[radio_id][if_id][dir].drops = 0; } } } return *offset; } static ssize_t rate_limit_set_proc(struct file *file, const char *buff, size_t len, loff_t *offset) { char input[PROC_CMD_MAX_LEN]; uint32_t rate, burst, radio_id, if_id, dir; char dev_name[32]; int ret; if (copy_from_user(input, buff, len) != 0) return -EFAULT; ret = sscanf(input, "%32s %u %u %u", dev_name, &dir, &rate, &burst); if (ret == 2 && !strcmp(dev_name, "tr")) { dbg_trace_count = dbg_trace_rate = dir; printk("Debug trace rate set to %u\n", dbg_trace_rate); return len; } if ((ret = sscanf(input, "%32s %u %u %u", dev_name, &dir, &rate, &burst)) < 4) goto Usage; if (!wfd_dev_by_name_get(dev_name, &radio_id, &if_id)) { printk("Couldn't find netdev \'%s\'", dev_name); return len; } if (rate > 10000 || burst > 10000 || rate > burst || dir > 1) goto Usage; dev_rate_limit[radio_id][if_id][dir].rate = MBITS_TO_BYTES(rate); dev_rate_limit[radio_id][if_id][dir].tokens = dev_rate_limit[radio_id][if_id][dir].burst = MBITS_TO_BYTES(burst); dev_rate_limit[radio_id][if_id][dir].last_ts = 0; printk("%s (radio %d, if %d) [%s]: set rate/burst %u Mbits/%u Mbits\n", dev_name, radio_id, if_id, dir == RL_DIR_RX ? "RX" : "TX", rate, burst); goto Exit; Usage: printk("\nUsage: <0(TX)/1(RX)> \nAcceptable range: 1 - 10000 Kbit/s, burst >= rate\n"); Exit: return len; } static uint32_t __rl_should_drop(rate_limit_t *rl, uint32_t size) { uint32_t diff_ts; if (!rl || !rl->rate) return 0; if (!rl->last_ts) { /* Initialize */ rl->last_ts = jiffies; return 0; } if ((diff_ts = jiffies - rl->last_ts)) { if (diff_ts < HZ) { rl_trace("diff_ts %u; size %u; tokens %u -> %u\n", diff_ts, size, rl->tokens, min((rl->tokens + (rl->rate*diff_ts)/HZ), rl->burst)); rl->tokens = min((rl->tokens + (rl->rate*diff_ts)/HZ), rl->burst); } else { rl_trace("diff_ts %u; size %u; tokens %u -> %u\n", diff_ts, size, rl->tokens, rl->burst); rl->tokens = rl->burst; } rl->last_ts = jiffies; } if (!rl->tokens || size > rl->tokens) { rl->drops++; rl_trace("dropped %d packets\n", rl->drops); return 1; } rl->tokens -= size; return 0; } uint32_t rl_should_drop(uint32_t wfd_id, uint32_t if_id, int dir, uint32_t size) { return __rl_should_drop(&dev_rate_limit[wfd_id][if_id][dir], size); } static uint32_t wl_chain_drop(rate_limit_t *rl, struct sk_buff *skb) { uint32_t drop_count = 0; struct sk_buff *next; next = bdmf_sysb_chain_next(skb); while (skb) { next = bdmf_sysb_chain_next(skb); bdmf_sysb_free(skb); rl->drops++; rl_trace("dropped %d packets\n", rl->drops); skb = next; drop_count++; } return drop_count; } /* Test whether the skbs chain fits into the given rate. The function can drop the whole chain or part of it (tail) * Return value: 0 - some part of the chain (1 or more packets from the head) should be transmitted * 1 - all packets were dropped */ uint32_t rl_chain_check_and_drop(uint32_t wfd_id, uint32_t if_id, int dir, struct sk_buff *skb) { int is_chained; struct sk_buff *prev = NULL, *curr = skb; rate_limit_t *rl = &dev_rate_limit[wfd_id][if_id][dir]; uint32_t drop_count; if (!rl->rate) return 0; is_chained = bdmf_sysb_is_chained(skb); if (__rl_should_drop(rl, skb->len)) { if (is_chained) { drop_count = wl_chain_drop(rl, skb); rl->drops += drop_count - 1; /* -1 because rl_should_drop increases the counter */ rl_trace("dropped %d packets\n", rl->drops); } else bdmf_sysb_free(skb); /* all packets are dropped */ return 1; } else if (!is_chained) return 0; /* single packet - can pass */ /* Go through the chain and check every packet if it fits into the given rate */ prev = skb; curr = bdmf_sysb_chain_next(skb); while (curr && !__rl_should_drop(rl, curr->len)) { prev = curr; curr = bdmf_sysb_chain_next(curr); } if (!curr) return 0; /* all tested packets can pass */ /* drop the rest of the chain */ bdmf_sysb_chain_link_set(prev, NULL); drop_count = wl_chain_drop(rl, curr); rl->drops += drop_count - 1; /* -1 because rl_should_drop increases the counter */ rl_trace("dropped %d packets\n", rl->drops); /* some packets from the head can pass */ return 0; } static const struct file_operations rate_limit_fops = { .read = rate_limit_get_proc, .write = rate_limit_set_proc, }; void rate_limiter_init(void) { rl_file_conf = proc_create("wfd/rate_limit", 0644, NULL, &rate_limit_fops); }