Add support for GRED offload.  It behaves much like RED, but
can apply different parameters to different bands.  GRED operates
pretty much exactly like our HW/FW with a single FIFO and different
RED state instances.

Signed-off-by: Jakub Kicinski <jakub.kicin...@netronome.com>
Reviewed-by: John Hurley <john.hur...@netronome.com>
---
 drivers/net/ethernet/netronome/nfp/abm/main.c |   2 +
 drivers/net/ethernet/netronome/nfp/abm/main.h |  12 +-
 .../net/ethernet/netronome/nfp/abm/qdisc.c    | 154 +++++++++++++++++-
 3 files changed, 158 insertions(+), 10 deletions(-)

diff --git a/drivers/net/ethernet/netronome/nfp/abm/main.c 
b/drivers/net/ethernet/netronome/nfp/abm/main.c
index b21250b95475..4f95f7b4430b 100644
--- a/drivers/net/ethernet/netronome/nfp/abm/main.c
+++ b/drivers/net/ethernet/netronome/nfp/abm/main.c
@@ -44,6 +44,8 @@ nfp_abm_setup_tc(struct nfp_app *app, struct net_device 
*netdev,
                return nfp_abm_setup_tc_mq(netdev, repr->app_priv, type_data);
        case TC_SETUP_QDISC_RED:
                return nfp_abm_setup_tc_red(netdev, repr->app_priv, type_data);
+       case TC_SETUP_QDISC_GRED:
+               return nfp_abm_setup_tc_gred(netdev, repr->app_priv, type_data);
        default:
                return -EOPNOTSUPP;
        }
diff --git a/drivers/net/ethernet/netronome/nfp/abm/main.h 
b/drivers/net/ethernet/netronome/nfp/abm/main.h
index 47888288a706..6bb4e60c1ad8 100644
--- a/drivers/net/ethernet/netronome/nfp/abm/main.h
+++ b/drivers/net/ethernet/netronome/nfp/abm/main.h
@@ -8,6 +8,7 @@
 #include <linux/radix-tree.h>
 #include <net/devlink.h>
 #include <net/pkt_cls.h>
+#include <net/pkt_sched.h>
 
 /* Dump of 64 PRIOs and 256 REDs seems to take 850us on Xeon v4 @ 2.20GHz;
  * 2.5ms / 400Hz seems more than sufficient for stats resolution.
@@ -89,6 +90,7 @@ enum nfp_qdisc_type {
        NFP_QDISC_NONE = 0,
        NFP_QDISC_MQ,
        NFP_QDISC_RED,
+       NFP_QDISC_GRED,
 };
 
 #define NFP_QDISC_UNTRACKED    ((struct nfp_qdisc *)1UL)
@@ -139,7 +141,7 @@ struct nfp_qdisc {
                        struct nfp_alink_stats stats;
                        struct nfp_alink_stats prev_stats;
                } mq;
-               /* TC_SETUP_QDISC_RED */
+               /* TC_SETUP_QDISC_RED, TC_SETUP_QDISC_GRED */
                struct {
                        unsigned int num_bands;
 
@@ -149,7 +151,7 @@ struct nfp_qdisc {
                                struct nfp_alink_stats prev_stats;
                                struct nfp_alink_xstats xstats;
                                struct nfp_alink_xstats prev_xstats;
-                       } band[1];
+                       } band[MAX_DPs];
                } red;
        };
 };
@@ -164,6 +166,8 @@ struct nfp_qdisc {
  *
  * @last_stats_update: ktime of last stats update
  *
+ * @def_band:          default band to use
+ *
  * @root_qdisc:        pointer to the current root of the Qdisc hierarchy
  * @qdiscs:    all qdiscs recorded by major part of the handle
  */
@@ -176,6 +180,8 @@ struct nfp_abm_link {
 
        u64 last_stats_update;
 
+       u8 def_band;
+
        struct nfp_qdisc *root_qdisc;
        struct radix_tree_root qdiscs;
 };
@@ -192,6 +198,8 @@ int nfp_abm_setup_tc_red(struct net_device *netdev, struct 
nfp_abm_link *alink,
                         struct tc_red_qopt_offload *opt);
 int nfp_abm_setup_tc_mq(struct net_device *netdev, struct nfp_abm_link *alink,
                        struct tc_mq_qopt_offload *opt);
+int nfp_abm_setup_tc_gred(struct net_device *netdev, struct nfp_abm_link 
*alink,
+                         struct tc_gred_qopt_offload *opt);
 
 void nfp_abm_ctrl_read_params(struct nfp_abm_link *alink);
 int nfp_abm_ctrl_find_addrs(struct nfp_abm *abm);
diff --git a/drivers/net/ethernet/netronome/nfp/abm/qdisc.c 
b/drivers/net/ethernet/netronome/nfp/abm/qdisc.c
index b65b3177c94a..e80a3d40a48b 100644
--- a/drivers/net/ethernet/netronome/nfp/abm/qdisc.c
+++ b/drivers/net/ethernet/netronome/nfp/abm/qdisc.c
@@ -15,7 +15,7 @@
 
 static bool nfp_abm_qdisc_is_red(struct nfp_qdisc *qdisc)
 {
-       return qdisc->type == NFP_QDISC_RED;
+       return qdisc->type == NFP_QDISC_RED || qdisc->type == NFP_QDISC_GRED;
 }
 
 static bool nfp_abm_qdisc_child_valid(struct nfp_qdisc *qdisc, unsigned int id)
@@ -191,12 +191,17 @@ static void
 nfp_abm_offload_compile_red(struct nfp_abm_link *alink, struct nfp_qdisc 
*qdisc,
                            unsigned int queue)
 {
+       bool good_red, good_gred;
        unsigned int i;
 
-       qdisc->offload_mark = qdisc->type == NFP_QDISC_RED &&
-                             qdisc->params_ok &&
-                             qdisc->use_cnt == 1 &&
-                             !qdisc->children[0];
+       good_red = qdisc->type == NFP_QDISC_RED &&
+                  qdisc->params_ok &&
+                  qdisc->use_cnt == 1 &&
+                  !qdisc->children[0];
+       good_gred = qdisc->type == NFP_QDISC_GRED &&
+                   qdisc->params_ok &&
+                   qdisc->use_cnt == 1;
+       qdisc->offload_mark = good_red || good_gred;
 
        /* If we are starting offload init prev_stats */
        if (qdisc->offload_mark && !qdisc->offloaded)
@@ -336,9 +341,11 @@ nfp_abm_qdisc_alloc(struct net_device *netdev, struct 
nfp_abm_link *alink,
        if (!qdisc)
                return NULL;
 
-       qdisc->children = kcalloc(children, sizeof(void *), GFP_KERNEL);
-       if (!qdisc->children)
-               goto err_free_qdisc;
+       if (children) {
+               qdisc->children = kcalloc(children, sizeof(void *), GFP_KERNEL);
+               if (!qdisc->children)
+                       goto err_free_qdisc;
+       }
 
        qdisc->netdev = netdev;
        qdisc->type = type;
@@ -464,6 +471,137 @@ nfp_abm_stats_red_calculate(struct nfp_alink_xstats *new,
        stats->pdrop += new->pdrop - old->pdrop;
 }
 
+static int
+nfp_abm_gred_stats(struct nfp_abm_link *alink, u32 handle,
+                  struct tc_gred_qopt_offload_stats *stats)
+{
+       struct nfp_qdisc *qdisc;
+       unsigned int i;
+
+       nfp_abm_stats_update(alink);
+
+       qdisc = nfp_abm_qdisc_find(alink, handle);
+       if (!qdisc)
+               return -EOPNOTSUPP;
+       /* If the qdisc offload has stopped we may need to adjust the backlog
+        * counters back so carry on even if qdisc is not currently offloaded.
+        */
+
+       for (i = 0; i < qdisc->red.num_bands; i++) {
+               if (!stats->xstats[i])
+                       continue;
+
+               nfp_abm_stats_calculate(&qdisc->red.band[i].stats,
+                                       &qdisc->red.band[i].prev_stats,
+                                       &stats->bstats[i], &stats->qstats[i]);
+               qdisc->red.band[i].prev_stats = qdisc->red.band[i].stats;
+
+               nfp_abm_stats_red_calculate(&qdisc->red.band[i].xstats,
+                                           &qdisc->red.band[i].prev_xstats,
+                                           stats->xstats[i]);
+               qdisc->red.band[i].prev_xstats = qdisc->red.band[i].xstats;
+       }
+
+       return qdisc->offloaded ? 0 : -EOPNOTSUPP;
+}
+
+static bool
+nfp_abm_gred_check_params(struct nfp_abm_link *alink,
+                         struct tc_gred_qopt_offload *opt)
+{
+       struct nfp_cpp *cpp = alink->abm->app->cpp;
+       struct nfp_abm *abm = alink->abm;
+       unsigned int i;
+
+       if (opt->set.grio_on || opt->set.wred_on) {
+               nfp_warn(cpp, "GRED offload failed - GRIO and WRED not 
supported (p:%08x h:%08x)\n",
+                        opt->parent, opt->handle);
+               return false;
+       }
+       if (opt->set.dp_def != alink->def_band) {
+               nfp_warn(cpp, "GRED offload failed - default band must be %d 
(p:%08x h:%08x)\n",
+                        alink->def_band, opt->parent, opt->handle);
+               return false;
+       }
+       if (opt->set.dp_cnt != abm->num_bands) {
+               nfp_warn(cpp, "GRED offload failed - band count must be %d 
(p:%08x h:%08x)\n",
+                        abm->num_bands, opt->parent, opt->handle);
+               return false;
+       }
+
+       for (i = 0; i < abm->num_bands; i++) {
+               struct tc_gred_vq_qopt_offload_params *band = &opt->set.tab[i];
+
+               if (!band->present)
+                       return false;
+               if (!band->is_ecn) {
+                       nfp_warn(cpp, "GRED offload failed - drop is not 
supported (ECN option required) (p:%08x h:%08x vq:%d)\n",
+                                opt->parent, opt->handle, i);
+                       return false;
+               }
+               if (band->is_harddrop) {
+                       nfp_warn(cpp, "GRED offload failed - harddrop is not 
supported (p:%08x h:%08x vq:%d)\n",
+                                opt->parent, opt->handle, i);
+                       return false;
+               }
+               if (band->min != band->max) {
+                       nfp_warn(cpp, "GRED offload failed - threshold mismatch 
(p:%08x h:%08x vq:%d)\n",
+                                opt->parent, opt->handle, i);
+                       return false;
+               }
+               if (band->min > S32_MAX) {
+                       nfp_warn(cpp, "GRED offload failed - threshold too 
large %d > %d (p:%08x h:%08x vq:%d)\n",
+                                band->min, S32_MAX, opt->parent, opt->handle,
+                                i);
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+static int
+nfp_abm_gred_replace(struct net_device *netdev, struct nfp_abm_link *alink,
+                    struct tc_gred_qopt_offload *opt)
+{
+       struct nfp_qdisc *qdisc;
+       unsigned int i;
+       int ret;
+
+       ret = nfp_abm_qdisc_replace(netdev, alink, NFP_QDISC_GRED, opt->parent,
+                                   opt->handle, 0, &qdisc);
+       if (ret < 0)
+               return ret;
+
+       qdisc->params_ok = nfp_abm_gred_check_params(alink, opt);
+       if (qdisc->params_ok) {
+               qdisc->red.num_bands = opt->set.dp_cnt;
+               for (i = 0; i < qdisc->red.num_bands; i++)
+                       qdisc->red.band[i].threshold = opt->set.tab[i].min;
+       }
+
+       if (qdisc->use_cnt)
+               nfp_abm_qdisc_offload_update(alink);
+
+       return 0;
+}
+
+int nfp_abm_setup_tc_gred(struct net_device *netdev, struct nfp_abm_link 
*alink,
+                         struct tc_gred_qopt_offload *opt)
+{
+       switch (opt->command) {
+       case TC_GRED_REPLACE:
+               return nfp_abm_gred_replace(netdev, alink, opt);
+       case TC_GRED_DESTROY:
+               nfp_abm_qdisc_destroy(netdev, alink, opt->handle);
+               return 0;
+       case TC_GRED_STATS:
+               return nfp_abm_gred_stats(alink, opt->handle, &opt->stats);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
 static int
 nfp_abm_red_xstats(struct nfp_abm_link *alink, struct tc_red_qopt_offload *opt)
 {
-- 
2.17.1

Reply via email to