mlxsw: spectrum_qdisc: Support offloading of TBF Qdisc
authorPetr Machata <petrm@mellanox.com>
Fri, 24 Jan 2020 13:23:14 +0000 (15:23 +0200)
committerDavid S. Miller <davem@davemloft.net>
Sat, 25 Jan 2020 09:56:31 +0000 (10:56 +0100)
React to the TC messages that were introduced in a preceding patch and
configure egress maximum shaper as appropriate. TBF can be used as a root
qdisc or under one of PRIO or strict ETS bands.

Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/mellanox/mlxsw/spectrum.c
drivers/net/ethernet/mellanox/mlxsw/spectrum.h
drivers/net/ethernet/mellanox/mlxsw/spectrum_qdisc.c

index 021664c..7358b5b 100644 (file)
@@ -1796,6 +1796,8 @@ static int mlxsw_sp_setup_tc(struct net_device *dev, enum tc_setup_type type,
                return mlxsw_sp_setup_tc_prio(mlxsw_sp_port, type_data);
        case TC_SETUP_QDISC_ETS:
                return mlxsw_sp_setup_tc_ets(mlxsw_sp_port, type_data);
+       case TC_SETUP_QDISC_TBF:
+               return mlxsw_sp_setup_tc_tbf(mlxsw_sp_port, type_data);
        default:
                return -EOPNOTSUPP;
        }
index a2393c0..a0f1f9d 100644 (file)
@@ -862,6 +862,8 @@ int mlxsw_sp_setup_tc_prio(struct mlxsw_sp_port *mlxsw_sp_port,
                           struct tc_prio_qopt_offload *p);
 int mlxsw_sp_setup_tc_ets(struct mlxsw_sp_port *mlxsw_sp_port,
                          struct tc_ets_qopt_offload *p);
+int mlxsw_sp_setup_tc_tbf(struct mlxsw_sp_port *mlxsw_sp_port,
+                         struct tc_tbf_qopt_offload *p);
 
 /* spectrum_fid.c */
 bool mlxsw_sp_fid_is_dummy(struct mlxsw_sp *mlxsw_sp, u16 fid_index);
index 57b014a..79a2801 100644 (file)
@@ -19,6 +19,7 @@ enum mlxsw_sp_qdisc_type {
        MLXSW_SP_QDISC_RED,
        MLXSW_SP_QDISC_PRIO,
        MLXSW_SP_QDISC_ETS,
+       MLXSW_SP_QDISC_TBF,
 };
 
 struct mlxsw_sp_qdisc_ops {
@@ -540,6 +541,204 @@ int mlxsw_sp_setup_tc_red(struct mlxsw_sp_port *mlxsw_sp_port,
        }
 }
 
+static void
+mlxsw_sp_setup_tc_qdisc_leaf_clean_stats(struct mlxsw_sp_port *mlxsw_sp_port,
+                                        struct mlxsw_sp_qdisc *mlxsw_sp_qdisc)
+{
+       u64 backlog_cells = 0;
+       u64 tx_packets = 0;
+       u64 tx_bytes = 0;
+       u64 drops = 0;
+
+       mlxsw_sp_qdisc_collect_tc_stats(mlxsw_sp_port, mlxsw_sp_qdisc,
+                                       &tx_bytes, &tx_packets,
+                                       &drops, &backlog_cells);
+
+       mlxsw_sp_qdisc->stats_base.tx_packets = tx_packets;
+       mlxsw_sp_qdisc->stats_base.tx_bytes = tx_bytes;
+       mlxsw_sp_qdisc->stats_base.drops = drops;
+       mlxsw_sp_qdisc->stats_base.backlog = 0;
+}
+
+static int
+mlxsw_sp_qdisc_tbf_destroy(struct mlxsw_sp_port *mlxsw_sp_port,
+                          struct mlxsw_sp_qdisc *mlxsw_sp_qdisc)
+{
+       struct mlxsw_sp_qdisc *root_qdisc = mlxsw_sp_port->root_qdisc;
+
+       if (root_qdisc != mlxsw_sp_qdisc)
+               root_qdisc->stats_base.backlog -=
+                                       mlxsw_sp_qdisc->stats_base.backlog;
+
+       return mlxsw_sp_port_ets_maxrate_set(mlxsw_sp_port,
+                                            MLXSW_REG_QEEC_HR_SUBGROUP,
+                                            mlxsw_sp_qdisc->tclass_num, 0,
+                                            MLXSW_REG_QEEC_MAS_DIS, 0);
+}
+
+static int
+mlxsw_sp_qdisc_tbf_bs(struct mlxsw_sp_port *mlxsw_sp_port,
+                     u32 max_size, u8 *p_burst_size)
+{
+       /* TBF burst size is configured in bytes. The ASIC burst size value is
+        * ((2 ^ bs) * 512 bits. Convert the TBF bytes to 512-bit units.
+        */
+       u32 bs512 = max_size / 64;
+       u8 bs = fls(bs512);
+
+       if (!bs)
+               return -EINVAL;
+       --bs;
+
+       /* Demand a power of two. */
+       if ((1 << bs) != bs512)
+               return -EINVAL;
+
+       if (bs < mlxsw_sp_port->mlxsw_sp->lowest_shaper_bs ||
+           bs > MLXSW_REG_QEEC_HIGHEST_SHAPER_BS)
+               return -EINVAL;
+
+       *p_burst_size = bs;
+       return 0;
+}
+
+static u32
+mlxsw_sp_qdisc_tbf_max_size(u8 bs)
+{
+       return (1U << bs) * 64;
+}
+
+static u64
+mlxsw_sp_qdisc_tbf_rate_kbps(struct tc_tbf_qopt_offload_replace_params *p)
+{
+       /* TBF interface is in bytes/s, whereas Spectrum ASIC is configured in
+        * Kbits/s.
+        */
+       return p->rate.rate_bytes_ps / 1000 * 8;
+}
+
+static int
+mlxsw_sp_qdisc_tbf_check_params(struct mlxsw_sp_port *mlxsw_sp_port,
+                               struct mlxsw_sp_qdisc *mlxsw_sp_qdisc,
+                               void *params)
+{
+       struct tc_tbf_qopt_offload_replace_params *p = params;
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
+       u64 rate_kbps = mlxsw_sp_qdisc_tbf_rate_kbps(p);
+       u8 burst_size;
+       int err;
+
+       if (rate_kbps >= MLXSW_REG_QEEC_MAS_DIS) {
+               dev_err(mlxsw_sp_port->mlxsw_sp->bus_info->dev,
+                       "spectrum: TBF: rate of %lluKbps must be below %u\n",
+                       rate_kbps, MLXSW_REG_QEEC_MAS_DIS);
+               return -EINVAL;
+       }
+
+       err = mlxsw_sp_qdisc_tbf_bs(mlxsw_sp_port, p->max_size, &burst_size);
+       if (err) {
+               u8 highest_shaper_bs = MLXSW_REG_QEEC_HIGHEST_SHAPER_BS;
+
+               dev_err(mlxsw_sp->bus_info->dev,
+                       "spectrum: TBF: invalid burst size of %u, must be a power of two between %u and %u",
+                       p->max_size,
+                       mlxsw_sp_qdisc_tbf_max_size(mlxsw_sp->lowest_shaper_bs),
+                       mlxsw_sp_qdisc_tbf_max_size(highest_shaper_bs));
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int
+mlxsw_sp_qdisc_tbf_replace(struct mlxsw_sp_port *mlxsw_sp_port,
+                          struct mlxsw_sp_qdisc *mlxsw_sp_qdisc,
+                          void *params)
+{
+       struct tc_tbf_qopt_offload_replace_params *p = params;
+       u64 rate_kbps = mlxsw_sp_qdisc_tbf_rate_kbps(p);
+       u8 burst_size;
+       int err;
+
+       err = mlxsw_sp_qdisc_tbf_bs(mlxsw_sp_port, p->max_size, &burst_size);
+       if (WARN_ON_ONCE(err))
+               /* check_params above was supposed to reject this value. */
+               return -EINVAL;
+
+       /* Configure subgroup shaper, so that both UC and MC traffic is subject
+        * to shaping. That is unlike RED, however UC queue lengths are going to
+        * be different than MC ones due to different pool and quota
+        * configurations, so the configuration is not applicable. For shaper on
+        * the other hand, subjecting the overall stream to the configured
+        * shaper makes sense. Also note that that is what we do for
+        * ieee_setmaxrate().
+        */
+       return mlxsw_sp_port_ets_maxrate_set(mlxsw_sp_port,
+                                            MLXSW_REG_QEEC_HR_SUBGROUP,
+                                            mlxsw_sp_qdisc->tclass_num, 0,
+                                            rate_kbps, burst_size);
+}
+
+static void
+mlxsw_sp_qdisc_tbf_unoffload(struct mlxsw_sp_port *mlxsw_sp_port,
+                            struct mlxsw_sp_qdisc *mlxsw_sp_qdisc,
+                            void *params)
+{
+       struct tc_tbf_qopt_offload_replace_params *p = params;
+
+       mlxsw_sp_qdisc_leaf_unoffload(mlxsw_sp_port, mlxsw_sp_qdisc, p->qstats);
+}
+
+static int
+mlxsw_sp_qdisc_get_tbf_stats(struct mlxsw_sp_port *mlxsw_sp_port,
+                            struct mlxsw_sp_qdisc *mlxsw_sp_qdisc,
+                            struct tc_qopt_offload_stats *stats_ptr)
+{
+       mlxsw_sp_qdisc_get_tc_stats(mlxsw_sp_port, mlxsw_sp_qdisc,
+                                   stats_ptr);
+       return 0;
+}
+
+static struct mlxsw_sp_qdisc_ops mlxsw_sp_qdisc_ops_tbf = {
+       .type = MLXSW_SP_QDISC_TBF,
+       .check_params = mlxsw_sp_qdisc_tbf_check_params,
+       .replace = mlxsw_sp_qdisc_tbf_replace,
+       .unoffload = mlxsw_sp_qdisc_tbf_unoffload,
+       .destroy = mlxsw_sp_qdisc_tbf_destroy,
+       .get_stats = mlxsw_sp_qdisc_get_tbf_stats,
+       .clean_stats = mlxsw_sp_setup_tc_qdisc_leaf_clean_stats,
+};
+
+int mlxsw_sp_setup_tc_tbf(struct mlxsw_sp_port *mlxsw_sp_port,
+                         struct tc_tbf_qopt_offload *p)
+{
+       struct mlxsw_sp_qdisc *mlxsw_sp_qdisc;
+
+       mlxsw_sp_qdisc = mlxsw_sp_qdisc_find(mlxsw_sp_port, p->parent, false);
+       if (!mlxsw_sp_qdisc)
+               return -EOPNOTSUPP;
+
+       if (p->command == TC_TBF_REPLACE)
+               return mlxsw_sp_qdisc_replace(mlxsw_sp_port, p->handle,
+                                             mlxsw_sp_qdisc,
+                                             &mlxsw_sp_qdisc_ops_tbf,
+                                             &p->replace_params);
+
+       if (!mlxsw_sp_qdisc_compare(mlxsw_sp_qdisc, p->handle,
+                                   MLXSW_SP_QDISC_TBF))
+               return -EOPNOTSUPP;
+
+       switch (p->command) {
+       case TC_TBF_DESTROY:
+               return mlxsw_sp_qdisc_destroy(mlxsw_sp_port, mlxsw_sp_qdisc);
+       case TC_TBF_STATS:
+               return mlxsw_sp_qdisc_get_stats(mlxsw_sp_port, mlxsw_sp_qdisc,
+                                               &p->stats);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
 static int
 __mlxsw_sp_qdisc_ets_destroy(struct mlxsw_sp_port *mlxsw_sp_port)
 {