net: mscc: ocelot: Implement port policers via tc command
authorJoergen Andreasen <joergen.andreasen@microchip.com>
Tue, 28 May 2019 12:49:17 +0000 (14:49 +0200)
committerDavid S. Miller <davem@davemloft.net>
Thu, 30 May 2019 04:37:49 +0000 (21:37 -0700)
Hardware offload of matchall classifier and police action are now
supported via the tc command.
Supported police parameters are: rate and burst.

Example:

Add:
tc qdisc add dev eth3 handle ffff: ingress
tc filter add dev eth3 parent ffff: prio 1 handle 2 \
matchall skip_sw \
action police rate 100Mbit burst 10000

Show:
tc -s -d qdisc show dev eth3
tc -s -d filter show dev eth3 ingress

Delete:
tc filter del dev eth3 parent ffff: prio 1
tc qdisc del dev eth3 handle ffff: ingress

Signed-off-by: Joergen Andreasen <joergen.andreasen@microchip.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/mscc/Makefile
drivers/net/ethernet/mscc/ocelot.c
drivers/net/ethernet/mscc/ocelot.h
drivers/net/ethernet/mscc/ocelot_police.c [new file with mode: 0644]
drivers/net/ethernet/mscc/ocelot_police.h [new file with mode: 0644]
drivers/net/ethernet/mscc/ocelot_tc.c [new file with mode: 0644]
drivers/net/ethernet/mscc/ocelot_tc.h [new file with mode: 0644]

index cb52a3b..5e694dc 100644 (file)
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: (GPL-2.0 OR MIT)
 obj-$(CONFIG_MSCC_OCELOT_SWITCH) += mscc_ocelot_common.o
 mscc_ocelot_common-y := ocelot.o ocelot_io.o
-mscc_ocelot_common-y += ocelot_regs.o
+mscc_ocelot_common-y += ocelot_regs.o ocelot_tc.o ocelot_police.o
 obj-$(CONFIG_MSCC_OCELOT_SWITCH_OCELOT) += ocelot_board.o
index d715ef4..ab7d9eb 100644 (file)
@@ -910,6 +910,13 @@ static int ocelot_set_features(struct net_device *dev,
        struct ocelot_port *port = netdev_priv(dev);
        netdev_features_t changed = dev->features ^ features;
 
+       if ((dev->features & NETIF_F_HW_TC) > (features & NETIF_F_HW_TC) &&
+           port->tc.offload_cnt) {
+               netdev_err(dev,
+                          "Cannot disable HW TC offload while offloads active\n");
+               return -EBUSY;
+       }
+
        if (changed & NETIF_F_HW_VLAN_CTAG_FILTER)
                ocelot_vlan_mode(port, features);
 
@@ -943,6 +950,7 @@ static const struct net_device_ops ocelot_port_netdev_ops = {
        .ndo_vlan_rx_kill_vid           = ocelot_vlan_rx_kill_vid,
        .ndo_set_features               = ocelot_set_features,
        .ndo_get_port_parent_id         = ocelot_get_port_parent_id,
+       .ndo_setup_tc                   = ocelot_setup_tc,
 };
 
 static void ocelot_get_strings(struct net_device *netdev, u32 sset, u8 *data)
@@ -1663,8 +1671,9 @@ int ocelot_probe_port(struct ocelot *ocelot, u8 port,
        dev->netdev_ops = &ocelot_port_netdev_ops;
        dev->ethtool_ops = &ocelot_ethtool_ops;
 
-       dev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_RXFCS;
-       dev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
+       dev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_RXFCS |
+               NETIF_F_HW_TC;
+       dev->features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_HW_TC;
 
        memcpy(dev->dev_addr, ocelot->base_mac, ETH_ALEN);
        dev->dev_addr[ETH_ALEN - 1] += port;
index ba3b338..9514979 100644 (file)
@@ -22,6 +22,7 @@
 #include "ocelot_rew.h"
 #include "ocelot_sys.h"
 #include "ocelot_qs.h"
+#include "ocelot_tc.h"
 
 #define PGID_AGGR    64
 #define PGID_SRC     80
@@ -458,6 +459,8 @@ struct ocelot_port {
 
        phy_interface_t phy_mode;
        struct phy *serdes;
+
+       struct ocelot_port_tc tc;
 };
 
 u32 __ocelot_read_ix(struct ocelot *ocelot, u32 reg, u32 offset);
diff --git a/drivers/net/ethernet/mscc/ocelot_police.c b/drivers/net/ethernet/mscc/ocelot_police.c
new file mode 100644 (file)
index 0000000..701e82d
--- /dev/null
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/* Microsemi Ocelot Switch driver
+ *
+ * Copyright (c) 2019 Microsemi Corporation
+ */
+
+#include "ocelot_police.h"
+
+enum mscc_qos_rate_mode {
+       MSCC_QOS_RATE_MODE_DISABLED, /* Policer/shaper disabled */
+       MSCC_QOS_RATE_MODE_LINE, /* Measure line rate in kbps incl. IPG */
+       MSCC_QOS_RATE_MODE_DATA, /* Measures data rate in kbps excl. IPG */
+       MSCC_QOS_RATE_MODE_FRAME, /* Measures frame rate in fps */
+       __MSCC_QOS_RATE_MODE_END,
+       NUM_MSCC_QOS_RATE_MODE = __MSCC_QOS_RATE_MODE_END,
+       MSCC_QOS_RATE_MODE_MAX = __MSCC_QOS_RATE_MODE_END - 1,
+};
+
+/* Types for ANA:POL[0-192]:POL_MODE_CFG.FRM_MODE */
+#define POL_MODE_LINERATE   0 /* Incl IPG. Unit: 33 1/3 kbps, 4096 bytes */
+#define POL_MODE_DATARATE   1 /* Excl IPG. Unit: 33 1/3 kbps, 4096 bytes  */
+#define POL_MODE_FRMRATE_HI 2 /* Unit: 33 1/3 fps, 32.8 frames */
+#define POL_MODE_FRMRATE_LO 3 /* Unit: 1/3 fps, 0.3 frames */
+
+/* Policer indexes */
+#define POL_IX_PORT    0    /* 0-11    : Port policers */
+#define POL_IX_QUEUE   32   /* 32-127  : Queue policers  */
+
+/* Default policer order */
+#define POL_ORDER 0x1d3 /* Ocelot policer order: Serial (QoS -> Port -> VCAP) */
+
+struct qos_policer_conf {
+       enum mscc_qos_rate_mode mode;
+       bool dlb; /* Enable DLB (dual leaky bucket mode */
+       bool cf;  /* Coupling flag (ignored in SLB mode) */
+       u32  cir; /* CIR in kbps/fps (ignored in SLB mode) */
+       u32  cbs; /* CBS in bytes/frames (ignored in SLB mode) */
+       u32  pir; /* PIR in kbps/fps */
+       u32  pbs; /* PBS in bytes/frames */
+       u8   ipg; /* Size of IPG when MSCC_QOS_RATE_MODE_LINE is chosen */
+};
+
+static int qos_policer_conf_set(struct ocelot_port *port, u32 pol_ix,
+                               struct qos_policer_conf *conf)
+{
+       u32 cf = 0, cir_ena = 0, frm_mode = POL_MODE_LINERATE;
+       u32 cir = 0, cbs = 0, pir = 0, pbs = 0;
+       bool cir_discard = 0, pir_discard = 0;
+       struct ocelot *ocelot = port->ocelot;
+       u32 pbs_max = 0, cbs_max = 0;
+       u8 ipg = 20;
+       u32 value;
+
+       pir = conf->pir;
+       pbs = conf->pbs;
+
+       switch (conf->mode) {
+       case MSCC_QOS_RATE_MODE_LINE:
+       case MSCC_QOS_RATE_MODE_DATA:
+               if (conf->mode == MSCC_QOS_RATE_MODE_LINE) {
+                       frm_mode = POL_MODE_LINERATE;
+                       ipg = min_t(u8, GENMASK(4, 0), conf->ipg);
+               } else {
+                       frm_mode = POL_MODE_DATARATE;
+               }
+               if (conf->dlb) {
+                       cir_ena = 1;
+                       cir = conf->cir;
+                       cbs = conf->cbs;
+                       if (cir == 0 && cbs == 0) {
+                               /* Discard cir frames */
+                               cir_discard = 1;
+                       } else {
+                               cir = DIV_ROUND_UP(cir, 100);
+                               cir *= 3; /* 33 1/3 kbps */
+                               cbs = DIV_ROUND_UP(cbs, 4096);
+                               cbs = (cbs ? cbs : 1); /* No zero burst size */
+                               cbs_max = 60; /* Limit burst size */
+                               cf = conf->cf;
+                               if (cf)
+                                       pir += conf->cir;
+                       }
+               }
+               if (pir == 0 && pbs == 0) {
+                       /* Discard PIR frames */
+                       pir_discard = 1;
+               } else {
+                       pir = DIV_ROUND_UP(pir, 100);
+                       pir *= 3;  /* 33 1/3 kbps */
+                       pbs = DIV_ROUND_UP(pbs, 4096);
+                       pbs = (pbs ? pbs : 1); /* No zero burst size */
+                       pbs_max = 60; /* Limit burst size */
+               }
+               break;
+       case MSCC_QOS_RATE_MODE_FRAME:
+               if (pir >= 100) {
+                       frm_mode = POL_MODE_FRMRATE_HI;
+                       pir = DIV_ROUND_UP(pir, 100);
+                       pir *= 3;  /* 33 1/3 fps */
+                       pbs = (pbs * 10) / 328; /* 32.8 frames */
+                       pbs = (pbs ? pbs : 1); /* No zero burst size */
+                       pbs_max = GENMASK(6, 0); /* Limit burst size */
+               } else {
+                       frm_mode = POL_MODE_FRMRATE_LO;
+                       if (pir == 0 && pbs == 0) {
+                               /* Discard all frames */
+                               pir_discard = 1;
+                               cir_discard = 1;
+                       } else {
+                               pir *= 3; /* 1/3 fps */
+                               pbs = (pbs * 10) / 3; /* 0.3 frames */
+                               pbs = (pbs ? pbs : 1); /* No zero burst size */
+                               pbs_max = 61; /* Limit burst size */
+                       }
+               }
+               break;
+       default: /* MSCC_QOS_RATE_MODE_DISABLED */
+               /* Disable policer using maximum rate and zero burst */
+               pir = GENMASK(15, 0);
+               pbs = 0;
+               break;
+       }
+
+       /* Check limits */
+       if (pir > GENMASK(15, 0)) {
+               netdev_err(port->dev, "Invalid pir\n");
+               return -EINVAL;
+       }
+
+       if (cir > GENMASK(15, 0)) {
+               netdev_err(port->dev, "Invalid cir\n");
+               return -EINVAL;
+       }
+
+       if (pbs > pbs_max) {
+               netdev_err(port->dev, "Invalid pbs\n");
+               return -EINVAL;
+       }
+
+       if (cbs > cbs_max) {
+               netdev_err(port->dev, "Invalid cbs\n");
+               return -EINVAL;
+       }
+
+       value = (ANA_POL_MODE_CFG_IPG_SIZE(ipg) |
+                ANA_POL_MODE_CFG_FRM_MODE(frm_mode) |
+                (cf ? ANA_POL_MODE_CFG_DLB_COUPLED : 0) |
+                (cir_ena ? ANA_POL_MODE_CFG_CIR_ENA : 0) |
+                ANA_POL_MODE_CFG_OVERSHOOT_ENA);
+
+       ocelot_write_gix(ocelot, value, ANA_POL_MODE_CFG, pol_ix);
+
+       ocelot_write_gix(ocelot,
+                        ANA_POL_PIR_CFG_PIR_RATE(pir) |
+                        ANA_POL_PIR_CFG_PIR_BURST(pbs),
+                        ANA_POL_PIR_CFG, pol_ix);
+
+       ocelot_write_gix(ocelot,
+                        (pir_discard ? GENMASK(22, 0) : 0),
+                        ANA_POL_PIR_STATE, pol_ix);
+
+       ocelot_write_gix(ocelot,
+                        ANA_POL_CIR_CFG_CIR_RATE(cir) |
+                        ANA_POL_CIR_CFG_CIR_BURST(cbs),
+                        ANA_POL_CIR_CFG, pol_ix);
+
+       ocelot_write_gix(ocelot,
+                        (cir_discard ? GENMASK(22, 0) : 0),
+                        ANA_POL_CIR_STATE, pol_ix);
+
+       return 0;
+}
+
+int ocelot_port_policer_add(struct ocelot_port *port,
+                           struct ocelot_policer *pol)
+{
+       struct ocelot *ocelot = port->ocelot;
+       struct qos_policer_conf pp = { 0 };
+       int err;
+
+       if (!pol)
+               return -EINVAL;
+
+       pp.mode = MSCC_QOS_RATE_MODE_DATA;
+       pp.pir = pol->rate;
+       pp.pbs = pol->burst;
+
+       netdev_dbg(port->dev,
+                  "%s: port %u pir %u kbps, pbs %u bytes\n",
+                  __func__, port->chip_port, pp.pir, pp.pbs);
+
+       err = qos_policer_conf_set(port, POL_IX_PORT + port->chip_port, &pp);
+       if (err)
+               return err;
+
+       ocelot_rmw_gix(ocelot,
+                      ANA_PORT_POL_CFG_PORT_POL_ENA |
+                      ANA_PORT_POL_CFG_POL_ORDER(POL_ORDER),
+                      ANA_PORT_POL_CFG_PORT_POL_ENA |
+                      ANA_PORT_POL_CFG_POL_ORDER_M,
+                      ANA_PORT_POL_CFG, port->chip_port);
+
+       return 0;
+}
+
+int ocelot_port_policer_del(struct ocelot_port *port)
+{
+       struct ocelot *ocelot = port->ocelot;
+       struct qos_policer_conf pp = { 0 };
+       int err;
+
+       netdev_dbg(port->dev, "%s: port %u\n", __func__, port->chip_port);
+
+       pp.mode = MSCC_QOS_RATE_MODE_DISABLED;
+
+       err = qos_policer_conf_set(port, POL_IX_PORT + port->chip_port, &pp);
+       if (err)
+               return err;
+
+       ocelot_rmw_gix(ocelot,
+                      ANA_PORT_POL_CFG_POL_ORDER(POL_ORDER),
+                      ANA_PORT_POL_CFG_PORT_POL_ENA |
+                      ANA_PORT_POL_CFG_POL_ORDER_M,
+                      ANA_PORT_POL_CFG, port->chip_port);
+
+       return 0;
+}
diff --git a/drivers/net/ethernet/mscc/ocelot_police.h b/drivers/net/ethernet/mscc/ocelot_police.h
new file mode 100644 (file)
index 0000000..d1137f7
--- /dev/null
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/* Microsemi Ocelot Switch driver
+ *
+ * Copyright (c) 2019 Microsemi Corporation
+ */
+
+#ifndef _MSCC_OCELOT_POLICE_H_
+#define _MSCC_OCELOT_POLICE_H_
+
+#include "ocelot.h"
+
+struct ocelot_policer {
+       u32 rate; /* kilobit per second */
+       u32 burst; /* bytes */
+};
+
+int ocelot_port_policer_add(struct ocelot_port *port,
+                           struct ocelot_policer *pol);
+
+int ocelot_port_policer_del(struct ocelot_port *port);
+
+#endif /* _MSCC_OCELOT_POLICE_H_ */
diff --git a/drivers/net/ethernet/mscc/ocelot_tc.c b/drivers/net/ethernet/mscc/ocelot_tc.c
new file mode 100644 (file)
index 0000000..a0eaadc
--- /dev/null
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/* Microsemi Ocelot Switch TC driver
+ *
+ * Copyright (c) 2019 Microsemi Corporation
+ */
+
+#include "ocelot_tc.h"
+#include "ocelot_police.h"
+#include <net/pkt_cls.h>
+
+static int ocelot_setup_tc_cls_matchall(struct ocelot_port *port,
+                                       struct tc_cls_matchall_offload *f,
+                                       bool ingress)
+{
+       struct netlink_ext_ack *extack = f->common.extack;
+       struct ocelot_policer pol = { 0 };
+       struct flow_action_entry *action;
+       int err;
+
+       netdev_dbg(port->dev, "%s: port %u command %d cookie %lu\n",
+                  __func__, port->chip_port, f->command, f->cookie);
+
+       if (!ingress) {
+               NL_SET_ERR_MSG_MOD(extack, "Only ingress is supported");
+               return -EOPNOTSUPP;
+       }
+
+       switch (f->command) {
+       case TC_CLSMATCHALL_REPLACE:
+               if (!flow_offload_has_one_action(&f->rule->action)) {
+                       NL_SET_ERR_MSG_MOD(extack,
+                                          "Only one action is supported");
+                       return -EOPNOTSUPP;
+               }
+
+               if (port->tc.block_shared) {
+                       NL_SET_ERR_MSG_MOD(extack,
+                                          "Rate limit is not supported on shared blocks");
+                       return -EOPNOTSUPP;
+               }
+
+               action = &f->rule->action.entries[0];
+
+               if (action->id != FLOW_ACTION_POLICE) {
+                       NL_SET_ERR_MSG_MOD(extack, "Unsupported action");
+                       return -EOPNOTSUPP;
+               }
+
+               if (port->tc.police_id && port->tc.police_id != f->cookie) {
+                       NL_SET_ERR_MSG_MOD(extack,
+                                          "Only one policer per port is supported\n");
+                       return -EEXIST;
+               }
+
+               pol.rate = (u32)div_u64(action->police.rate_bytes_ps, 1000) * 8;
+               pol.burst = (u32)div_u64(action->police.rate_bytes_ps *
+                                        PSCHED_NS2TICKS(action->police.burst),
+                                        PSCHED_TICKS_PER_SEC);
+
+               err = ocelot_port_policer_add(port, &pol);
+               if (err) {
+                       NL_SET_ERR_MSG_MOD(extack, "Could not add policer\n");
+                       return err;
+               }
+
+               port->tc.police_id = f->cookie;
+               port->tc.offload_cnt++;
+               return 0;
+       case TC_CLSMATCHALL_DESTROY:
+               if (port->tc.police_id != f->cookie)
+                       return -ENOENT;
+
+               err = ocelot_port_policer_del(port);
+               if (err) {
+                       NL_SET_ERR_MSG_MOD(extack,
+                                          "Could not delete policer\n");
+                       return err;
+               }
+               port->tc.police_id = 0;
+               port->tc.offload_cnt--;
+               return 0;
+       case TC_CLSMATCHALL_STATS: /* fall through */
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static int ocelot_setup_tc_block_cb(enum tc_setup_type type,
+                                   void *type_data,
+                                   void *cb_priv, bool ingress)
+{
+       struct ocelot_port *port = cb_priv;
+
+       if (!tc_cls_can_offload_and_chain0(port->dev, type_data))
+               return -EOPNOTSUPP;
+
+       switch (type) {
+       case TC_SETUP_CLSMATCHALL:
+               netdev_dbg(port->dev, "tc_block_cb: TC_SETUP_CLSMATCHALL %s\n",
+                          ingress ? "ingress" : "egress");
+
+               return ocelot_setup_tc_cls_matchall(port, type_data, ingress);
+       case TC_SETUP_CLSFLOWER:
+               netdev_dbg(port->dev, "tc_block_cb: TC_SETUP_CLSFLOWER %s\n",
+                          ingress ? "ingress" : "egress");
+
+               return -EOPNOTSUPP;
+       default:
+               netdev_dbg(port->dev, "tc_block_cb: type %d %s\n",
+                          type,
+                          ingress ? "ingress" : "egress");
+
+               return -EOPNOTSUPP;
+       }
+}
+
+static int ocelot_setup_tc_block_cb_ig(enum tc_setup_type type,
+                                      void *type_data,
+                                      void *cb_priv)
+{
+       return ocelot_setup_tc_block_cb(type, type_data,
+                                       cb_priv, true);
+}
+
+static int ocelot_setup_tc_block_cb_eg(enum tc_setup_type type,
+                                      void *type_data,
+                                      void *cb_priv)
+{
+       return ocelot_setup_tc_block_cb(type, type_data,
+                                       cb_priv, false);
+}
+
+static int ocelot_setup_tc_block(struct ocelot_port *port,
+                                struct tc_block_offload *f)
+{
+       tc_setup_cb_t *cb;
+
+       netdev_dbg(port->dev, "tc_block command %d, binder_type %d\n",
+                  f->command, f->binder_type);
+
+       if (f->binder_type == TCF_BLOCK_BINDER_TYPE_CLSACT_INGRESS) {
+               cb = ocelot_setup_tc_block_cb_ig;
+               port->tc.block_shared = tcf_block_shared(f->block);
+       } else if (f->binder_type == TCF_BLOCK_BINDER_TYPE_CLSACT_EGRESS) {
+               cb = ocelot_setup_tc_block_cb_eg;
+       } else {
+               return -EOPNOTSUPP;
+       }
+
+       switch (f->command) {
+       case TC_BLOCK_BIND:
+               return tcf_block_cb_register(f->block, cb, port,
+                                            port, f->extack);
+       case TC_BLOCK_UNBIND:
+               tcf_block_cb_unregister(f->block, cb, port);
+               return 0;
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+int ocelot_setup_tc(struct net_device *dev, enum tc_setup_type type,
+                   void *type_data)
+{
+       struct ocelot_port *port = netdev_priv(dev);
+
+       switch (type) {
+       case TC_SETUP_BLOCK:
+               return ocelot_setup_tc_block(port, type_data);
+       default:
+               return -EOPNOTSUPP;
+       }
+       return 0;
+}
diff --git a/drivers/net/ethernet/mscc/ocelot_tc.h b/drivers/net/ethernet/mscc/ocelot_tc.h
new file mode 100644 (file)
index 0000000..61757c2
--- /dev/null
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/* Microsemi Ocelot Switch driver
+ *
+ * Copyright (c) 2019 Microsemi Corporation
+ */
+
+#ifndef _MSCC_OCELOT_TC_H_
+#define _MSCC_OCELOT_TC_H_
+
+#include <linux/netdevice.h>
+
+struct ocelot_port_tc {
+       bool block_shared;
+       unsigned long offload_cnt;
+
+       unsigned long police_id;
+};
+
+int ocelot_setup_tc(struct net_device *dev, enum tc_setup_type type,
+                   void *type_data);
+
+#endif /* _MSCC_OCELOT_TC_H_ */