196f17e07SNogah Frankel /* 296f17e07SNogah Frankel * drivers/net/ethernet/mellanox/mlxsw/spectrum_qdisc.c 396f17e07SNogah Frankel * Copyright (c) 2017 Mellanox Technologies. All rights reserved. 496f17e07SNogah Frankel * Copyright (c) 2017 Nogah Frankel <nogahf@mellanox.com> 596f17e07SNogah Frankel * 696f17e07SNogah Frankel * Redistribution and use in source and binary forms, with or without 796f17e07SNogah Frankel * modification, are permitted provided that the following conditions are met: 896f17e07SNogah Frankel * 996f17e07SNogah Frankel * 1. Redistributions of source code must retain the above copyright 1096f17e07SNogah Frankel * notice, this list of conditions and the following disclaimer. 1196f17e07SNogah Frankel * 2. Redistributions in binary form must reproduce the above copyright 1296f17e07SNogah Frankel * notice, this list of conditions and the following disclaimer in the 1396f17e07SNogah Frankel * documentation and/or other materials provided with the distribution. 1496f17e07SNogah Frankel * 3. Neither the names of the copyright holders nor the names of its 1596f17e07SNogah Frankel * contributors may be used to endorse or promote products derived from 1696f17e07SNogah Frankel * this software without specific prior written permission. 1796f17e07SNogah Frankel * 1896f17e07SNogah Frankel * Alternatively, this software may be distributed under the terms of the 1996f17e07SNogah Frankel * GNU General Public License ("GPL") version 2 as published by the Free 2096f17e07SNogah Frankel * Software Foundation. 2196f17e07SNogah Frankel * 2296f17e07SNogah Frankel * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 2396f17e07SNogah Frankel * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2496f17e07SNogah Frankel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2596f17e07SNogah Frankel * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 2696f17e07SNogah Frankel * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 2796f17e07SNogah Frankel * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 2896f17e07SNogah Frankel * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 2996f17e07SNogah Frankel * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 3096f17e07SNogah Frankel * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 3196f17e07SNogah Frankel * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 3296f17e07SNogah Frankel * POSSIBILITY OF SUCH DAMAGE. 3396f17e07SNogah Frankel */ 3496f17e07SNogah Frankel 3596f17e07SNogah Frankel #include <linux/kernel.h> 3696f17e07SNogah Frankel #include <linux/errno.h> 3796f17e07SNogah Frankel #include <linux/netdevice.h> 3896f17e07SNogah Frankel #include <net/pkt_cls.h> 39861fb829SNogah Frankel #include <net/red.h> 4096f17e07SNogah Frankel 4196f17e07SNogah Frankel #include "spectrum.h" 4296f17e07SNogah Frankel #include "reg.h" 4396f17e07SNogah Frankel 44371b437aSNogah Frankel enum mlxsw_sp_qdisc_type { 45371b437aSNogah Frankel MLXSW_SP_QDISC_NO_QDISC, 46371b437aSNogah Frankel MLXSW_SP_QDISC_RED, 47371b437aSNogah Frankel }; 48371b437aSNogah Frankel 49371b437aSNogah Frankel struct mlxsw_sp_qdisc { 50371b437aSNogah Frankel u32 handle; 51371b437aSNogah Frankel enum mlxsw_sp_qdisc_type type; 52371b437aSNogah Frankel struct red_stats xstats_base; 53371b437aSNogah Frankel union { 54371b437aSNogah Frankel struct { 55371b437aSNogah Frankel u64 tail_drop_base; 56371b437aSNogah Frankel u64 ecn_base; 57371b437aSNogah Frankel u64 wred_drop_base; 58371b437aSNogah Frankel } red; 59371b437aSNogah Frankel } xstats; 60371b437aSNogah Frankel u64 tx_bytes; 61371b437aSNogah Frankel u64 tx_packets; 62371b437aSNogah Frankel u64 drops; 63371b437aSNogah Frankel u64 overlimits; 64371b437aSNogah Frankel }; 65371b437aSNogah Frankel 6696f17e07SNogah Frankel static int 6796f17e07SNogah Frankel mlxsw_sp_tclass_congestion_enable(struct mlxsw_sp_port *mlxsw_sp_port, 6896f17e07SNogah Frankel int tclass_num, u32 min, u32 max, 6996f17e07SNogah Frankel u32 probability, bool is_ecn) 7096f17e07SNogah Frankel { 7196f17e07SNogah Frankel char cwtp_cmd[max_t(u8, MLXSW_REG_CWTP_LEN, MLXSW_REG_CWTPM_LEN)]; 7296f17e07SNogah Frankel struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; 7396f17e07SNogah Frankel int err; 7496f17e07SNogah Frankel 7596f17e07SNogah Frankel mlxsw_reg_cwtp_pack(cwtp_cmd, mlxsw_sp_port->local_port, tclass_num); 7696f17e07SNogah Frankel mlxsw_reg_cwtp_profile_pack(cwtp_cmd, MLXSW_REG_CWTP_DEFAULT_PROFILE, 7796f17e07SNogah Frankel roundup(min, MLXSW_REG_CWTP_MIN_VALUE), 7896f17e07SNogah Frankel roundup(max, MLXSW_REG_CWTP_MIN_VALUE), 7996f17e07SNogah Frankel probability); 8096f17e07SNogah Frankel 8196f17e07SNogah Frankel err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(cwtp), cwtp_cmd); 8296f17e07SNogah Frankel if (err) 8396f17e07SNogah Frankel return err; 8496f17e07SNogah Frankel 8596f17e07SNogah Frankel mlxsw_reg_cwtpm_pack(cwtp_cmd, mlxsw_sp_port->local_port, tclass_num, 8696f17e07SNogah Frankel MLXSW_REG_CWTP_DEFAULT_PROFILE, true, is_ecn); 8796f17e07SNogah Frankel 8896f17e07SNogah Frankel return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(cwtpm), cwtp_cmd); 8996f17e07SNogah Frankel } 9096f17e07SNogah Frankel 9196f17e07SNogah Frankel static int 9296f17e07SNogah Frankel mlxsw_sp_tclass_congestion_disable(struct mlxsw_sp_port *mlxsw_sp_port, 9396f17e07SNogah Frankel int tclass_num) 9496f17e07SNogah Frankel { 9596f17e07SNogah Frankel struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; 9696f17e07SNogah Frankel char cwtpm_cmd[MLXSW_REG_CWTPM_LEN]; 9796f17e07SNogah Frankel 9896f17e07SNogah Frankel mlxsw_reg_cwtpm_pack(cwtpm_cmd, mlxsw_sp_port->local_port, tclass_num, 9996f17e07SNogah Frankel MLXSW_REG_CWTPM_RESET_PROFILE, false, false); 10096f17e07SNogah Frankel return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(cwtpm), cwtpm_cmd); 10196f17e07SNogah Frankel } 10296f17e07SNogah Frankel 103861fb829SNogah Frankel static void 104861fb829SNogah Frankel mlxsw_sp_setup_tc_qdisc_clean_stats(struct mlxsw_sp_port *mlxsw_sp_port, 105861fb829SNogah Frankel struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, 106861fb829SNogah Frankel int tclass_num) 107861fb829SNogah Frankel { 108861fb829SNogah Frankel struct red_stats *xstats_base = &mlxsw_sp_qdisc->xstats_base; 109861fb829SNogah Frankel struct mlxsw_sp_port_xstats *xstats; 1103670756fSNogah Frankel struct rtnl_link_stats64 *stats; 111861fb829SNogah Frankel 112861fb829SNogah Frankel xstats = &mlxsw_sp_port->periodic_hw_stats.xstats; 1133670756fSNogah Frankel stats = &mlxsw_sp_port->periodic_hw_stats.stats; 1143670756fSNogah Frankel 1153670756fSNogah Frankel mlxsw_sp_qdisc->tx_packets = stats->tx_packets; 1163670756fSNogah Frankel mlxsw_sp_qdisc->tx_bytes = stats->tx_bytes; 117861fb829SNogah Frankel 118861fb829SNogah Frankel switch (mlxsw_sp_qdisc->type) { 119861fb829SNogah Frankel case MLXSW_SP_QDISC_RED: 120861fb829SNogah Frankel xstats_base->prob_mark = xstats->ecn; 121861fb829SNogah Frankel xstats_base->prob_drop = xstats->wred_drop[tclass_num]; 122861fb829SNogah Frankel xstats_base->pdrop = xstats->tail_drop[tclass_num]; 1233670756fSNogah Frankel 1243670756fSNogah Frankel mlxsw_sp_qdisc->overlimits = xstats_base->prob_drop + 1253670756fSNogah Frankel xstats_base->prob_mark; 1263670756fSNogah Frankel mlxsw_sp_qdisc->drops = xstats_base->prob_drop + 1273670756fSNogah Frankel xstats_base->pdrop; 128861fb829SNogah Frankel break; 129861fb829SNogah Frankel default: 130861fb829SNogah Frankel break; 131861fb829SNogah Frankel } 132861fb829SNogah Frankel } 133861fb829SNogah Frankel 13496f17e07SNogah Frankel static int 13596f17e07SNogah Frankel mlxsw_sp_qdisc_red_destroy(struct mlxsw_sp_port *mlxsw_sp_port, u32 handle, 13696f17e07SNogah Frankel struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, 13796f17e07SNogah Frankel int tclass_num) 13896f17e07SNogah Frankel { 13996f17e07SNogah Frankel int err; 14096f17e07SNogah Frankel 14196f17e07SNogah Frankel if (mlxsw_sp_qdisc->handle != handle) 14296f17e07SNogah Frankel return 0; 14396f17e07SNogah Frankel 14496f17e07SNogah Frankel err = mlxsw_sp_tclass_congestion_disable(mlxsw_sp_port, tclass_num); 14596f17e07SNogah Frankel mlxsw_sp_qdisc->handle = TC_H_UNSPEC; 14696f17e07SNogah Frankel mlxsw_sp_qdisc->type = MLXSW_SP_QDISC_NO_QDISC; 14796f17e07SNogah Frankel 14896f17e07SNogah Frankel return err; 14996f17e07SNogah Frankel } 15096f17e07SNogah Frankel 15196f17e07SNogah Frankel static int 15296f17e07SNogah Frankel mlxsw_sp_qdisc_red_replace(struct mlxsw_sp_port *mlxsw_sp_port, u32 handle, 15396f17e07SNogah Frankel struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, 15496f17e07SNogah Frankel int tclass_num, 15596f17e07SNogah Frankel struct tc_red_qopt_offload_params *p) 15696f17e07SNogah Frankel { 15796f17e07SNogah Frankel struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; 15896f17e07SNogah Frankel u32 min, max; 15996f17e07SNogah Frankel u64 prob; 16096f17e07SNogah Frankel int err = 0; 16196f17e07SNogah Frankel 16296f17e07SNogah Frankel if (p->min > p->max) { 16396f17e07SNogah Frankel dev_err(mlxsw_sp->bus_info->dev, 16496f17e07SNogah Frankel "spectrum: RED: min %u is bigger then max %u\n", p->min, 16596f17e07SNogah Frankel p->max); 16696f17e07SNogah Frankel goto err_bad_param; 16796f17e07SNogah Frankel } 16896f17e07SNogah Frankel if (p->max > MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_BUFFER_SIZE)) { 16996f17e07SNogah Frankel dev_err(mlxsw_sp->bus_info->dev, 17096f17e07SNogah Frankel "spectrum: RED: max value %u is too big\n", p->max); 17196f17e07SNogah Frankel goto err_bad_param; 17296f17e07SNogah Frankel } 17396f17e07SNogah Frankel if (p->min == 0 || p->max == 0) { 17496f17e07SNogah Frankel dev_err(mlxsw_sp->bus_info->dev, 17596f17e07SNogah Frankel "spectrum: RED: 0 value is illegal for min and max\n"); 17696f17e07SNogah Frankel goto err_bad_param; 17796f17e07SNogah Frankel } 17896f17e07SNogah Frankel 17996f17e07SNogah Frankel /* calculate probability in percentage */ 18096f17e07SNogah Frankel prob = p->probability; 18196f17e07SNogah Frankel prob *= 100; 18296f17e07SNogah Frankel prob = DIV_ROUND_UP(prob, 1 << 16); 18396f17e07SNogah Frankel prob = DIV_ROUND_UP(prob, 1 << 16); 18496f17e07SNogah Frankel min = mlxsw_sp_bytes_cells(mlxsw_sp, p->min); 18596f17e07SNogah Frankel max = mlxsw_sp_bytes_cells(mlxsw_sp, p->max); 18696f17e07SNogah Frankel err = mlxsw_sp_tclass_congestion_enable(mlxsw_sp_port, tclass_num, min, 18796f17e07SNogah Frankel max, prob, p->is_ecn); 18896f17e07SNogah Frankel if (err) 18996f17e07SNogah Frankel goto err_config; 19096f17e07SNogah Frankel 19196f17e07SNogah Frankel mlxsw_sp_qdisc->type = MLXSW_SP_QDISC_RED; 192861fb829SNogah Frankel if (mlxsw_sp_qdisc->handle != handle) 193861fb829SNogah Frankel mlxsw_sp_setup_tc_qdisc_clean_stats(mlxsw_sp_port, 194861fb829SNogah Frankel mlxsw_sp_qdisc, 195861fb829SNogah Frankel tclass_num); 196861fb829SNogah Frankel 19796f17e07SNogah Frankel mlxsw_sp_qdisc->handle = handle; 19896f17e07SNogah Frankel return 0; 19996f17e07SNogah Frankel 20096f17e07SNogah Frankel err_bad_param: 20196f17e07SNogah Frankel err = -EINVAL; 20296f17e07SNogah Frankel err_config: 20396f17e07SNogah Frankel mlxsw_sp_qdisc_red_destroy(mlxsw_sp_port, mlxsw_sp_qdisc->handle, 20496f17e07SNogah Frankel mlxsw_sp_qdisc, tclass_num); 20596f17e07SNogah Frankel return err; 20696f17e07SNogah Frankel } 20796f17e07SNogah Frankel 208861fb829SNogah Frankel static int 209861fb829SNogah Frankel mlxsw_sp_qdisc_get_red_xstats(struct mlxsw_sp_port *mlxsw_sp_port, u32 handle, 210861fb829SNogah Frankel struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, 211861fb829SNogah Frankel int tclass_num, struct red_stats *res) 212861fb829SNogah Frankel { 213861fb829SNogah Frankel struct red_stats *xstats_base = &mlxsw_sp_qdisc->xstats_base; 214861fb829SNogah Frankel struct mlxsw_sp_port_xstats *xstats; 215861fb829SNogah Frankel 216861fb829SNogah Frankel if (mlxsw_sp_qdisc->handle != handle || 217861fb829SNogah Frankel mlxsw_sp_qdisc->type != MLXSW_SP_QDISC_RED) 218861fb829SNogah Frankel return -EOPNOTSUPP; 219861fb829SNogah Frankel 220861fb829SNogah Frankel xstats = &mlxsw_sp_port->periodic_hw_stats.xstats; 221861fb829SNogah Frankel 222861fb829SNogah Frankel res->prob_drop = xstats->wred_drop[tclass_num] - xstats_base->prob_drop; 223861fb829SNogah Frankel res->prob_mark = xstats->ecn - xstats_base->prob_mark; 224861fb829SNogah Frankel res->pdrop = xstats->tail_drop[tclass_num] - xstats_base->pdrop; 225861fb829SNogah Frankel return 0; 226861fb829SNogah Frankel } 227861fb829SNogah Frankel 2283670756fSNogah Frankel static int 2293670756fSNogah Frankel mlxsw_sp_qdisc_get_red_stats(struct mlxsw_sp_port *mlxsw_sp_port, u32 handle, 2303670756fSNogah Frankel struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, 2313670756fSNogah Frankel int tclass_num, 2323670756fSNogah Frankel struct tc_red_qopt_offload_stats *res) 2333670756fSNogah Frankel { 2343670756fSNogah Frankel u64 tx_bytes, tx_packets, overlimits, drops; 2353670756fSNogah Frankel struct mlxsw_sp_port_xstats *xstats; 2363670756fSNogah Frankel struct rtnl_link_stats64 *stats; 2373670756fSNogah Frankel 2383670756fSNogah Frankel if (mlxsw_sp_qdisc->handle != handle || 2393670756fSNogah Frankel mlxsw_sp_qdisc->type != MLXSW_SP_QDISC_RED) 2403670756fSNogah Frankel return -EOPNOTSUPP; 2413670756fSNogah Frankel 2423670756fSNogah Frankel xstats = &mlxsw_sp_port->periodic_hw_stats.xstats; 2433670756fSNogah Frankel stats = &mlxsw_sp_port->periodic_hw_stats.stats; 2443670756fSNogah Frankel 2453670756fSNogah Frankel tx_bytes = stats->tx_bytes - mlxsw_sp_qdisc->tx_bytes; 2463670756fSNogah Frankel tx_packets = stats->tx_packets - mlxsw_sp_qdisc->tx_packets; 2473670756fSNogah Frankel overlimits = xstats->wred_drop[tclass_num] + xstats->ecn - 2483670756fSNogah Frankel mlxsw_sp_qdisc->overlimits; 2493670756fSNogah Frankel drops = xstats->wred_drop[tclass_num] + xstats->tail_drop[tclass_num] - 2503670756fSNogah Frankel mlxsw_sp_qdisc->drops; 2513670756fSNogah Frankel 2523670756fSNogah Frankel _bstats_update(res->bstats, tx_bytes, tx_packets); 2533670756fSNogah Frankel res->qstats->overlimits += overlimits; 2543670756fSNogah Frankel res->qstats->drops += drops; 2553670756fSNogah Frankel res->qstats->backlog += mlxsw_sp_cells_bytes(mlxsw_sp_port->mlxsw_sp, 2563670756fSNogah Frankel xstats->backlog[tclass_num]); 2573670756fSNogah Frankel 2583670756fSNogah Frankel mlxsw_sp_qdisc->drops += drops; 2593670756fSNogah Frankel mlxsw_sp_qdisc->overlimits += overlimits; 2603670756fSNogah Frankel mlxsw_sp_qdisc->tx_bytes += tx_bytes; 2613670756fSNogah Frankel mlxsw_sp_qdisc->tx_packets += tx_packets; 2623670756fSNogah Frankel return 0; 2633670756fSNogah Frankel } 2643670756fSNogah Frankel 26596f17e07SNogah Frankel #define MLXSW_SP_PORT_DEFAULT_TCLASS 0 26696f17e07SNogah Frankel 26796f17e07SNogah Frankel int mlxsw_sp_setup_tc_red(struct mlxsw_sp_port *mlxsw_sp_port, 26896f17e07SNogah Frankel struct tc_red_qopt_offload *p) 26996f17e07SNogah Frankel { 27096f17e07SNogah Frankel struct mlxsw_sp_qdisc *mlxsw_sp_qdisc; 27196f17e07SNogah Frankel int tclass_num; 27296f17e07SNogah Frankel 27396f17e07SNogah Frankel if (p->parent != TC_H_ROOT) 27496f17e07SNogah Frankel return -EOPNOTSUPP; 27596f17e07SNogah Frankel 276371b437aSNogah Frankel mlxsw_sp_qdisc = mlxsw_sp_port->root_qdisc; 27796f17e07SNogah Frankel tclass_num = MLXSW_SP_PORT_DEFAULT_TCLASS; 27896f17e07SNogah Frankel 27996f17e07SNogah Frankel switch (p->command) { 28096f17e07SNogah Frankel case TC_RED_REPLACE: 28196f17e07SNogah Frankel return mlxsw_sp_qdisc_red_replace(mlxsw_sp_port, p->handle, 28296f17e07SNogah Frankel mlxsw_sp_qdisc, tclass_num, 28396f17e07SNogah Frankel &p->set); 28496f17e07SNogah Frankel case TC_RED_DESTROY: 28596f17e07SNogah Frankel return mlxsw_sp_qdisc_red_destroy(mlxsw_sp_port, p->handle, 28696f17e07SNogah Frankel mlxsw_sp_qdisc, tclass_num); 287861fb829SNogah Frankel case TC_RED_XSTATS: 288861fb829SNogah Frankel return mlxsw_sp_qdisc_get_red_xstats(mlxsw_sp_port, p->handle, 289861fb829SNogah Frankel mlxsw_sp_qdisc, tclass_num, 290861fb829SNogah Frankel p->xstats); 2913670756fSNogah Frankel case TC_RED_STATS: 2923670756fSNogah Frankel return mlxsw_sp_qdisc_get_red_stats(mlxsw_sp_port, p->handle, 2933670756fSNogah Frankel mlxsw_sp_qdisc, tclass_num, 2943670756fSNogah Frankel &p->stats); 29596f17e07SNogah Frankel default: 29696f17e07SNogah Frankel return -EOPNOTSUPP; 29796f17e07SNogah Frankel } 29896f17e07SNogah Frankel } 299371b437aSNogah Frankel 300371b437aSNogah Frankel int mlxsw_sp_tc_qdisc_init(struct mlxsw_sp_port *mlxsw_sp_port) 301371b437aSNogah Frankel { 302371b437aSNogah Frankel mlxsw_sp_port->root_qdisc = kzalloc(sizeof(*mlxsw_sp_port->root_qdisc), 303371b437aSNogah Frankel GFP_KERNEL); 304371b437aSNogah Frankel if (!mlxsw_sp_port->root_qdisc) 305371b437aSNogah Frankel return -ENOMEM; 306371b437aSNogah Frankel 307371b437aSNogah Frankel return 0; 308371b437aSNogah Frankel } 309371b437aSNogah Frankel 310371b437aSNogah Frankel void mlxsw_sp_tc_qdisc_fini(struct mlxsw_sp_port *mlxsw_sp_port) 311371b437aSNogah Frankel { 312371b437aSNogah Frankel kfree(mlxsw_sp_port->root_qdisc); 313371b437aSNogah Frankel } 314