diff mbox series

[net-next,2/3] net: mscc: ocelot: Implement port policers via tc command

Message ID 20190502094029.22526-3-joergen.andreasen@microchip.com (mailing list archive)
State Changes Requested
Headers show
Series Add hw offload of TC police on MSCC ocelot | expand

Commit Message

Joergen Andreasen May 2, 2019, 9:40 a.m. UTC
Hardware offload of port policers are now supported via the tc command.
Supported police parameters are: rate, burst and overhead.

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 overhead 20

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>
---
 drivers/net/ethernet/mscc/Makefile        |   2 +-
 drivers/net/ethernet/mscc/ocelot.c        |   6 +-
 drivers/net/ethernet/mscc/ocelot.h        |   3 +
 drivers/net/ethernet/mscc/ocelot_police.c | 289 ++++++++++++++++++++++
 drivers/net/ethernet/mscc/ocelot_police.h |  16 ++
 drivers/net/ethernet/mscc/ocelot_tc.c     | 151 +++++++++++
 drivers/net/ethernet/mscc/ocelot_tc.h     |  19 ++
 7 files changed, 483 insertions(+), 3 deletions(-)
 create mode 100644 drivers/net/ethernet/mscc/ocelot_police.c
 create mode 100644 drivers/net/ethernet/mscc/ocelot_police.h
 create mode 100644 drivers/net/ethernet/mscc/ocelot_tc.c
 create mode 100644 drivers/net/ethernet/mscc/ocelot_tc.h

Comments

Andrew Lunn May 2, 2019, 12:32 p.m. UTC | #1
Hi Joergen

> +
> +#define MSCC_RC(expr)				\
> +	do {					\
> +		int __rc__ = (expr);		\
> +		if (__rc__ < 0)			\
> +			return __rc__;		\
> +	}					\
> +	while (0)

I'm sure checkpatch warned about this. A return inside a macros is a
bad idea. I inherited code doing this, and broke it when adding
locking, because it was not obvious there was a return.

> +
> +/* The following two functions do the same as in iproute2 */
> +#define TIME_UNITS_PER_SEC	1000000
> +static unsigned int tc_core_tick2time(unsigned int tick)
> +{
> +	return (tick * (u32)PSCHED_TICKS2NS(1)) / 1000;
> +}
> +
> +static unsigned int tc_calc_xmitsize(u64 rate, unsigned int ticks)
> +{
> +	return div_u64(rate * tc_core_tick2time(ticks), TIME_UNITS_PER_SEC);
> +}

Should these but put somewhere others can use them?

> +
> +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,
> +};
> +
> +/* Round x divided by y to nearest integer. x and y are integers */
> +#define MSCC_ROUNDING_DIVISION(x, y) (((x) + ((y) / 2)) / (y))

linux/kernel.h defines DIV_ROUND_UP(). Maybe add DIV_ROUND_DOWN()?

> +
> +/* Round x divided by y to nearest higher integer. x and y are integers */
> +#define MSCC_DIV_ROUND_UP(x, y) (((x) + (y) - 1) / (y))

DIV_ROUND_UP() ?

> +	/* Limit to maximum values */
> +	pir = min_t(u32, GENMASK(15, 0), pir);
> +	cir = min_t(u32, GENMASK(15, 0), cir);
> +	pbs = min_t(u32, pbs_max, pbs);
> +	cbs = min_t(u32, cbs_max, cbs);

If it does need to limit, maybe return -EOPNOTSUPP?

> +int ocelot_port_policer_add(struct ocelot_port *port,
> +			    struct tcf_police *p)
> +{
> +	struct ocelot *ocelot = port->ocelot;
> +	struct qos_policer_conf pp;
> +
> +	if (!p)
> +		return -EINVAL;
> +
> +	netdev_dbg(port->dev,
> +		   "result %d ewma_rate %u burst %lld mtu %u mtu_pktoks %lld\n",
> +		   p->params->tcfp_result,
> +		   p->params->tcfp_ewma_rate,
> +		   p->params->tcfp_burst,
> +		   p->params->tcfp_mtu,
> +		   p->params->tcfp_mtu_ptoks);
> +
> +	if (p->params->rate_present)
> +		netdev_dbg(port->dev,
> +			   "rate: rate %llu mult %u over %u link %u shift %u\n",
> +			   p->params->rate.rate_bytes_ps,
> +			   p->params->rate.mult,
> +			   p->params->rate.overhead,
> +			   p->params->rate.linklayer,
> +			   p->params->rate.shift);
> +
> +	if (p->params->peak_present)
> +		netdev_dbg(port->dev,
> +			   "peak: rate %llu mult %u over %u link %u shift %u\n",
> +			   p->params->peak.rate_bytes_ps,
> +			   p->params->peak.mult,
> +			   p->params->peak.overhead,
> +			   p->params->peak.linklayer,
> +			   p->params->peak.shift);
> +
> +	memset(&pp, 0, sizeof(pp));

Rather than memset, you can do:

	struct qos_policer_conf pp = { 0 };

	Andrew
Jiri Pirko May 2, 2019, 8:36 p.m. UTC | #2
Thu, May 02, 2019 at 11:40:28AM CEST, joergen.andreasen@microchip.com wrote:
>Hardware offload of port policers are now supported via the tc command.
>Supported police parameters are: rate, burst and overhead.

Interesting, you offload matchall cls, yet you don't mention it at all.
Please, do it.


>
>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 overhead 20
>
>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>
>---
> drivers/net/ethernet/mscc/Makefile        |   2 +-
> drivers/net/ethernet/mscc/ocelot.c        |   6 +-
> drivers/net/ethernet/mscc/ocelot.h        |   3 +
> drivers/net/ethernet/mscc/ocelot_police.c | 289 ++++++++++++++++++++++
> drivers/net/ethernet/mscc/ocelot_police.h |  16 ++
> drivers/net/ethernet/mscc/ocelot_tc.c     | 151 +++++++++++
> drivers/net/ethernet/mscc/ocelot_tc.h     |  19 ++
> 7 files changed, 483 insertions(+), 3 deletions(-)
> create mode 100644 drivers/net/ethernet/mscc/ocelot_police.c
> create mode 100644 drivers/net/ethernet/mscc/ocelot_police.h
> create mode 100644 drivers/net/ethernet/mscc/ocelot_tc.c
> create mode 100644 drivers/net/ethernet/mscc/ocelot_tc.h
>
>diff --git a/drivers/net/ethernet/mscc/Makefile b/drivers/net/ethernet/mscc/Makefile
>index cb52a3b128ae..5e694dc1f7f8 100644
>--- a/drivers/net/ethernet/mscc/Makefile
>+++ b/drivers/net/ethernet/mscc/Makefile
>@@ -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
>diff --git a/drivers/net/ethernet/mscc/ocelot.c b/drivers/net/ethernet/mscc/ocelot.c
>index d715ef4fc92f..3ec7864d9dc8 100644
>--- a/drivers/net/ethernet/mscc/ocelot.c
>+++ b/drivers/net/ethernet/mscc/ocelot.c
>@@ -943,6 +943,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 +1664,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;
>diff --git a/drivers/net/ethernet/mscc/ocelot.h b/drivers/net/ethernet/mscc/ocelot.h
>index ba3b3380b4d0..9514979fa075 100644
>--- a/drivers/net/ethernet/mscc/ocelot.h
>+++ b/drivers/net/ethernet/mscc/ocelot.h
>@@ -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
>index 000000000000..b40382dcc748
>--- /dev/null
>+++ b/drivers/net/ethernet/mscc/ocelot_police.c
>@@ -0,0 +1,289 @@
>+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
>+/* Microsemi Ocelot Switch TC driver

"TC driver" ? That sounds quite odd...


>+ *
>+ * Copyright (c) 2019 Microsemi Corporation
>+ */
>+
>+#include "ocelot_police.h"
>+
>+#define MSCC_RC(expr)				\
>+	do {					\
>+		int __rc__ = (expr);		\
>+		if (__rc__ < 0)			\
>+			return __rc__;		\
>+	}					\
>+	while (0)

Please don't use macros like this.


>+
>+/* The following two functions do the same as in iproute2 */
>+#define TIME_UNITS_PER_SEC	1000000
>+static unsigned int tc_core_tick2time(unsigned int tick)
>+{
>+	return (tick * (u32)PSCHED_TICKS2NS(1)) / 1000;
>+}
>+
>+static unsigned int tc_calc_xmitsize(u64 rate, unsigned int ticks)
>+{
>+	return div_u64(rate * tc_core_tick2time(ticks), TIME_UNITS_PER_SEC);
>+}
>+
>+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,
>+};
>+
>+/* Round x divided by y to nearest integer. x and y are integers */
>+#define MSCC_ROUNDING_DIVISION(x, y) (((x) + ((y) / 2)) / (y))
>+
>+/* Round x divided by y to nearest higher integer. x and y are integers */
>+#define MSCC_DIV_ROUND_UP(x, y) (((x) + (y) - 1) / (y))
>+
>+/* 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)
>+{
>+	struct ocelot *ocelot = port->ocelot;
>+	u32 cir = 0, cbs = 0, pir = 0, pbs = 0;
>+	u32 cf = 0, cir_ena = 0, frm_mode = 0;
>+	u32 pbs_max = 0, cbs_max = 0;
>+	bool cir_discard = 0, pir_discard = 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 = MSCC_DIV_ROUND_UP(cir, 100);
>+				cir *= 3; /* 33 1/3 kbps */
>+				cbs = MSCC_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 = MSCC_DIV_ROUND_UP(pir, 100);
>+			pir *= 3;  /* 33 1/3 kbps */
>+			pbs = MSCC_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 = MSCC_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;
>+	}
>+
>+	/* Limit to maximum values */
>+	pir = min_t(u32, GENMASK(15, 0), pir);
>+	cir = min_t(u32, GENMASK(15, 0), cir);
>+	pbs = min_t(u32, pbs_max, pbs);
>+	cbs = min_t(u32, cbs_max, cbs);
>+
>+	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);

I understand that you want to wrap all 5 calls in the same way. But
still, pol_ix does not have to be on a separate line.


>+
>+	return 0;
>+}
>+
>+int ocelot_port_policer_add(struct ocelot_port *port,
>+			    struct tcf_police *p)
>+{
>+	struct ocelot *ocelot = port->ocelot;
>+	struct qos_policer_conf pp;
>+
>+	if (!p)
>+		return -EINVAL;
>+
>+	netdev_dbg(port->dev,

Unnecessary line wrap.


>+		   "result %d ewma_rate %u burst %lld mtu %u mtu_pktoks %lld\n",
>+		   p->params->tcfp_result,
>+		   p->params->tcfp_ewma_rate,
>+		   p->params->tcfp_burst,
>+		   p->params->tcfp_mtu,
>+		   p->params->tcfp_mtu_ptoks);
>+
>+	if (p->params->rate_present)
>+		netdev_dbg(port->dev,

Again, no need to wrap.


>+			   "rate: rate %llu mult %u over %u link %u shift %u\n",
>+			   p->params->rate.rate_bytes_ps,
>+			   p->params->rate.mult,
>+			   p->params->rate.overhead,
>+			   p->params->rate.linklayer,
>+			   p->params->rate.shift);
>+
>+	if (p->params->peak_present)
>+		netdev_dbg(port->dev,

Again, no need to wrap.


>+			   "peak: rate %llu mult %u over %u link %u shift %u\n",
>+			   p->params->peak.rate_bytes_ps,
>+			   p->params->peak.mult,
>+			   p->params->peak.overhead,
>+			   p->params->peak.linklayer,
>+			   p->params->peak.shift);
>+
>+	memset(&pp, 0, sizeof(pp));
>+
>+	if (p->params->tcfp_ewma_rate) {
>+		netdev_err(port->dev, "tcfp_ewma_rate is not supported\n");
>+		return -EOPNOTSUPP;
>+	}
>+	if (p->params->peak_present) {
>+		netdev_err(port->dev, "peakrate is not supported\n");
>+		return -EOPNOTSUPP;
>+	}
>+	if (!p->params->rate_present) {
>+		netdev_err(port->dev, "rate not specified\n");
>+		return -EINVAL;
>+	}
>+	if (p->params->rate.overhead) {
>+		pp.mode = MSCC_QOS_RATE_MODE_LINE;
>+		pp.ipg = p->params->rate.overhead;
>+	} else {
>+		pp.mode = MSCC_QOS_RATE_MODE_DATA;
>+	}
>+
>+	pp.pir = (u32)div_u64(p->params->rate.rate_bytes_ps, 1000) * 8;
>+	pp.pbs = tc_calc_xmitsize(p->params->rate.rate_bytes_ps,
>+				  PSCHED_NS2TICKS(p->params->tcfp_burst));
>+	netdev_dbg(port->dev,

Again, no need to wrap.


>+		   "%s: port %u pir %u kbps, pbs %u bytes, ipg %u bytes\n",
>+		   __func__, port->chip_port, pp.pir, pp.pbs, pp.ipg);
>+
>+	MSCC_RC(qos_policer_conf_set(port, POL_IX_PORT + port->chip_port, &pp));
>+
>+	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;
>+
>+	netdev_dbg(port->dev, "%s: port %u\n", __func__, port->chip_port);
>+
>+	memset(&pp, 0, sizeof(pp));
>+	pp.mode = MSCC_QOS_RATE_MODE_DISABLED;
>+
>+	MSCC_RC(qos_policer_conf_set(port, POL_IX_PORT + port->chip_port, &pp));
>+
>+	ocelot_rmw_gix(ocelot, 0 |
>+		       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
>index 000000000000..bc4dc34c684e
>--- /dev/null
>+++ b/drivers/net/ethernet/mscc/ocelot_police.h
>@@ -0,0 +1,16 @@
>+/* 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 <net/tc_act/tc_police.h>
>+#include "ocelot.h"
>+
>+int ocelot_port_policer_add(struct ocelot_port *port, struct tcf_police *p);
>+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
>index 000000000000..97b0a7bf5d06
>--- /dev/null
>+++ b/drivers/net/ethernet/mscc/ocelot_tc.c
>@@ -0,0 +1,151 @@
>+// 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)
>+{
>+	const struct tc_action *a;
>+	int err;
>+
>+	netdev_dbg(port->dev,

Again, no need to wrap.


>+		   "%s: port %u cookie %lu\n",
>+		   __func__, port->chip_port, f->cookie);
>+	switch (f->command) {
>+	case TC_CLSMATCHALL_REPLACE:
>+		if (!tcf_exts_has_one_action(f->exts)) {
>+			netdev_err(port->dev, "only one action is supported\n");
>+			return -EOPNOTSUPP;
>+		}
>+
>+		a = tcf_exts_first_action(f->exts);
>+
>+		if (is_tcf_police(a)) {
>+			if (!ingress)
>+				return -EOPNOTSUPP;
>+
>+			if (port->tc.police_id &&
>+			    port->tc.police_id != f->cookie) {
>+				netdev_warn(port->dev,

Again, no need to wrap.


>+					    "Only one policer per port is supported\n");
>+				return -EEXIST;
>+			}
>+
>+			err = ocelot_port_policer_add(port, to_police(a));
>+			if (err) {
>+				netdev_err(port->dev, "Could not add policer\n");
>+				return err;
>+			}
>+			port->tc.police_id = f->cookie;
>+			return 0;
>+		} else {
>+			return -EOPNOTSUPP;
>+		}
>+	case TC_CLSMATCHALL_DESTROY:
>+		if (port->tc.police_id != f->cookie)
>+			return -ENOENT;
>+
>+		err = ocelot_port_policer_del(port);
>+		if (err) {
>+			netdev_err(port->dev, "Could not delete policer\n");
>+			return err;
>+		}
>+		port->tc.police_id = 0;
>+		return 0;
>+	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,

Again, no need to wrap.


>+					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,

Again, no need to wrap.


>+					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;
>+	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
>index 000000000000..c905b98b6b4c
>--- /dev/null
>+++ b/drivers/net/ethernet/mscc/ocelot_tc.h
>@@ -0,0 +1,19 @@
>+/* 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 {
>+	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_ */
>-- 
>2.17.1
>
Joergen Andreasen May 3, 2019, 11:23 a.m. UTC | #3
Hi Andrew,

The 05/02/2019 14:32, Andrew Lunn wrote:
> External E-Mail
> 
> 
> Hi Joergen
> 
> > +
> > +#define MSCC_RC(expr)				\
> > +	do {					\
> > +		int __rc__ = (expr);		\
> > +		if (__rc__ < 0)			\
> > +			return __rc__;		\
> > +	}					\
> > +	while (0)
> 
> I'm sure checkpatch warned about this. A return inside a macros is a
> bad idea. I inherited code doing this, and broke it when adding
> locking, because it was not obvious there was a return.
>

I saw the warning but I assumed that it wasn't a problem in this small context.
The macro will be removed in v2.

> > +
> > +/* The following two functions do the same as in iproute2 */
> > +#define TIME_UNITS_PER_SEC	1000000
> > +static unsigned int tc_core_tick2time(unsigned int tick)
> > +{
> > +	return (tick * (u32)PSCHED_TICKS2NS(1)) / 1000;
> > +}
> > +
> > +static unsigned int tc_calc_xmitsize(u64 rate, unsigned int ticks)
> > +{
> > +	return div_u64(rate * tc_core_tick2time(ticks), TIME_UNITS_PER_SEC);
> > +}
> 
> Should these but put somewhere others can use them?
>

It would be nice to put them in a more public place, but I am in doubt where to
put them and what to call them.

Maybe they belong in the new file: include/net/tc_act/tc_police.h.
Would that be ok?

> > +
> > +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,
> > +};
> > +
> > +/* Round x divided by y to nearest integer. x and y are integers */
> > +#define MSCC_ROUNDING_DIVISION(x, y) (((x) + ((y) / 2)) / (y))
> 
> linux/kernel.h defines DIV_ROUND_UP(). Maybe add DIV_ROUND_DOWN()?
>

This macro is currently not used and I will remove it in v2.

> > +
> > +/* Round x divided by y to nearest higher integer. x and y are integers */
> > +#define MSCC_DIV_ROUND_UP(x, y) (((x) + (y) - 1) / (y))
> 
> DIV_ROUND_UP() ?
>

I will use DIV_ROUND_UP() in v2.

> > +	/* Limit to maximum values */
> > +	pir = min_t(u32, GENMASK(15, 0), pir);
> > +	cir = min_t(u32, GENMASK(15, 0), cir);
> > +	pbs = min_t(u32, pbs_max, pbs);
> > +	cbs = min_t(u32, cbs_max, cbs);
> 
> If it does need to limit, maybe return -EOPNOTSUPP?
>

It seems fine to return -EOPBITSUPP here.
I will do that in v2.

> > +int ocelot_port_policer_add(struct ocelot_port *port,
> > +			    struct tcf_police *p)
> > +{
> > +	struct ocelot *ocelot = port->ocelot;
> > +	struct qos_policer_conf pp;
> > +
> > +	if (!p)
> > +		return -EINVAL;
> > +
> > +	netdev_dbg(port->dev,
> > +		   "result %d ewma_rate %u burst %lld mtu %u mtu_pktoks %lld\n",
> > +		   p->params->tcfp_result,
> > +		   p->params->tcfp_ewma_rate,
> > +		   p->params->tcfp_burst,
> > +		   p->params->tcfp_mtu,
> > +		   p->params->tcfp_mtu_ptoks);
> > +
> > +	if (p->params->rate_present)
> > +		netdev_dbg(port->dev,
> > +			   "rate: rate %llu mult %u over %u link %u shift %u\n",
> > +			   p->params->rate.rate_bytes_ps,
> > +			   p->params->rate.mult,
> > +			   p->params->rate.overhead,
> > +			   p->params->rate.linklayer,
> > +			   p->params->rate.shift);
> > +
> > +	if (p->params->peak_present)
> > +		netdev_dbg(port->dev,
> > +			   "peak: rate %llu mult %u over %u link %u shift %u\n",
> > +			   p->params->peak.rate_bytes_ps,
> > +			   p->params->peak.mult,
> > +			   p->params->peak.overhead,
> > +			   p->params->peak.linklayer,
> > +			   p->params->peak.shift);
> > +
> > +	memset(&pp, 0, sizeof(pp));
> 
> Rather than memset, you can do:
> 
> 	struct qos_policer_conf pp = { 0 };
>

I will do as you suggest in v2.

> 	Andrew
>
Joergen Andreasen May 3, 2019, 11:38 a.m. UTC | #4
Hi Jiri,

The 05/02/2019 22:36, Jiri Pirko wrote:
> External E-Mail
> 
> 
> Thu, May 02, 2019 at 11:40:28AM CEST, joergen.andreasen@microchip.com wrote:
> >Hardware offload of port policers are now supported via the tc command.
> >Supported police parameters are: rate, burst and overhead.
> 
> Interesting, you offload matchall cls, yet you don't mention it at all.
> Please, do it.
>

I will mention that we also offload matchall cls in v2.

> 
> >
> >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 overhead 20
> >
> >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>
> >---
> > drivers/net/ethernet/mscc/Makefile        |   2 +-
> > drivers/net/ethernet/mscc/ocelot.c        |   6 +-
> > drivers/net/ethernet/mscc/ocelot.h        |   3 +
> > drivers/net/ethernet/mscc/ocelot_police.c | 289 ++++++++++++++++++++++
> > drivers/net/ethernet/mscc/ocelot_police.h |  16 ++
> > drivers/net/ethernet/mscc/ocelot_tc.c     | 151 +++++++++++
> > drivers/net/ethernet/mscc/ocelot_tc.h     |  19 ++
> > 7 files changed, 483 insertions(+), 3 deletions(-)
> > create mode 100644 drivers/net/ethernet/mscc/ocelot_police.c
> > create mode 100644 drivers/net/ethernet/mscc/ocelot_police.h
> > create mode 100644 drivers/net/ethernet/mscc/ocelot_tc.c
> > create mode 100644 drivers/net/ethernet/mscc/ocelot_tc.h
> >
> >diff --git a/drivers/net/ethernet/mscc/Makefile b/drivers/net/ethernet/mscc/Makefile
> >index cb52a3b128ae..5e694dc1f7f8 100644
> >--- a/drivers/net/ethernet/mscc/Makefile
> >+++ b/drivers/net/ethernet/mscc/Makefile
> >@@ -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
> >diff --git a/drivers/net/ethernet/mscc/ocelot.c b/drivers/net/ethernet/mscc/ocelot.c
> >index d715ef4fc92f..3ec7864d9dc8 100644
> >--- a/drivers/net/ethernet/mscc/ocelot.c
> >+++ b/drivers/net/ethernet/mscc/ocelot.c
> >@@ -943,6 +943,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 +1664,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;
> >diff --git a/drivers/net/ethernet/mscc/ocelot.h b/drivers/net/ethernet/mscc/ocelot.h
> >index ba3b3380b4d0..9514979fa075 100644
> >--- a/drivers/net/ethernet/mscc/ocelot.h
> >+++ b/drivers/net/ethernet/mscc/ocelot.h
> >@@ -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
> >index 000000000000..b40382dcc748
> >--- /dev/null
> >+++ b/drivers/net/ethernet/mscc/ocelot_police.c
> >@@ -0,0 +1,289 @@
> >+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
> >+/* Microsemi Ocelot Switch TC driver
> 
> "TC driver" ? That sounds quite odd...
>

I will change it to "Microsemi Ocelot Switch driver" in v2.

> 
> >+ *
> >+ * Copyright (c) 2019 Microsemi Corporation
> >+ */
> >+
> >+#include "ocelot_police.h"
> >+
> >+#define MSCC_RC(expr)				\
> >+	do {					\
> >+		int __rc__ = (expr);		\
> >+		if (__rc__ < 0)			\
> >+			return __rc__;		\
> >+	}					\
> >+	while (0)
> 
> Please don't use macros like this.
>

This macro will be removed in v2.

> 
> >+
> >+/* The following two functions do the same as in iproute2 */
> >+#define TIME_UNITS_PER_SEC	1000000
> >+static unsigned int tc_core_tick2time(unsigned int tick)
> >+{
> >+	return (tick * (u32)PSCHED_TICKS2NS(1)) / 1000;
> >+}
> >+
> >+static unsigned int tc_calc_xmitsize(u64 rate, unsigned int ticks)
> >+{
> >+	return div_u64(rate * tc_core_tick2time(ticks), TIME_UNITS_PER_SEC);
> >+}
> >+
> >+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,
> >+};
> >+
> >+/* Round x divided by y to nearest integer. x and y are integers */
> >+#define MSCC_ROUNDING_DIVISION(x, y) (((x) + ((y) / 2)) / (y))
> >+
> >+/* Round x divided by y to nearest higher integer. x and y are integers */
> >+#define MSCC_DIV_ROUND_UP(x, y) (((x) + (y) - 1) / (y))
> >+
> >+/* 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)
> >+{
> >+	struct ocelot *ocelot = port->ocelot;
> >+	u32 cir = 0, cbs = 0, pir = 0, pbs = 0;
> >+	u32 cf = 0, cir_ena = 0, frm_mode = 0;
> >+	u32 pbs_max = 0, cbs_max = 0;
> >+	bool cir_discard = 0, pir_discard = 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 = MSCC_DIV_ROUND_UP(cir, 100);
> >+				cir *= 3; /* 33 1/3 kbps */
> >+				cbs = MSCC_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 = MSCC_DIV_ROUND_UP(pir, 100);
> >+			pir *= 3;  /* 33 1/3 kbps */
> >+			pbs = MSCC_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 = MSCC_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;
> >+	}
> >+
> >+	/* Limit to maximum values */
> >+	pir = min_t(u32, GENMASK(15, 0), pir);
> >+	cir = min_t(u32, GENMASK(15, 0), cir);
> >+	pbs = min_t(u32, pbs_max, pbs);
> >+	cbs = min_t(u32, cbs_max, cbs);
> >+
> >+	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);
> 
> I understand that you want to wrap all 5 calls in the same way. But
> still, pol_ix does not have to be on a separate line.
>

I will fix this and the folloeing unnecessary line wraps in v2.

> 
> >+
> >+	return 0;
> >+}
> >+
> >+int ocelot_port_policer_add(struct ocelot_port *port,
> >+			    struct tcf_police *p)
> >+{
> >+	struct ocelot *ocelot = port->ocelot;
> >+	struct qos_policer_conf pp;
> >+
> >+	if (!p)
> >+		return -EINVAL;
> >+
> >+	netdev_dbg(port->dev,
> 
> Unnecessary line wrap.
> 
> 
> >+		   "result %d ewma_rate %u burst %lld mtu %u mtu_pktoks %lld\n",
> >+		   p->params->tcfp_result,
> >+		   p->params->tcfp_ewma_rate,
> >+		   p->params->tcfp_burst,
> >+		   p->params->tcfp_mtu,
> >+		   p->params->tcfp_mtu_ptoks);
> >+
> >+	if (p->params->rate_present)
> >+		netdev_dbg(port->dev,
> 
> Again, no need to wrap.
> 
> 
> >+			   "rate: rate %llu mult %u over %u link %u shift %u\n",
> >+			   p->params->rate.rate_bytes_ps,
> >+			   p->params->rate.mult,
> >+			   p->params->rate.overhead,
> >+			   p->params->rate.linklayer,
> >+			   p->params->rate.shift);
> >+
> >+	if (p->params->peak_present)
> >+		netdev_dbg(port->dev,
> 
> Again, no need to wrap.
> 
> 
> >+			   "peak: rate %llu mult %u over %u link %u shift %u\n",
> >+			   p->params->peak.rate_bytes_ps,
> >+			   p->params->peak.mult,
> >+			   p->params->peak.overhead,
> >+			   p->params->peak.linklayer,
> >+			   p->params->peak.shift);
> >+
> >+	memset(&pp, 0, sizeof(pp));
> >+
> >+	if (p->params->tcfp_ewma_rate) {
> >+		netdev_err(port->dev, "tcfp_ewma_rate is not supported\n");
> >+		return -EOPNOTSUPP;
> >+	}
> >+	if (p->params->peak_present) {
> >+		netdev_err(port->dev, "peakrate is not supported\n");
> >+		return -EOPNOTSUPP;
> >+	}
> >+	if (!p->params->rate_present) {
> >+		netdev_err(port->dev, "rate not specified\n");
> >+		return -EINVAL;
> >+	}
> >+	if (p->params->rate.overhead) {
> >+		pp.mode = MSCC_QOS_RATE_MODE_LINE;
> >+		pp.ipg = p->params->rate.overhead;
> >+	} else {
> >+		pp.mode = MSCC_QOS_RATE_MODE_DATA;
> >+	}
> >+
> >+	pp.pir = (u32)div_u64(p->params->rate.rate_bytes_ps, 1000) * 8;
> >+	pp.pbs = tc_calc_xmitsize(p->params->rate.rate_bytes_ps,
> >+				  PSCHED_NS2TICKS(p->params->tcfp_burst));
> >+	netdev_dbg(port->dev,
> 
> Again, no need to wrap.
> 
> 
> >+		   "%s: port %u pir %u kbps, pbs %u bytes, ipg %u bytes\n",
> >+		   __func__, port->chip_port, pp.pir, pp.pbs, pp.ipg);
> >+
> >+	MSCC_RC(qos_policer_conf_set(port, POL_IX_PORT + port->chip_port, &pp));
> >+
> >+	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;
> >+
> >+	netdev_dbg(port->dev, "%s: port %u\n", __func__, port->chip_port);
> >+
> >+	memset(&pp, 0, sizeof(pp));
> >+	pp.mode = MSCC_QOS_RATE_MODE_DISABLED;
> >+
> >+	MSCC_RC(qos_policer_conf_set(port, POL_IX_PORT + port->chip_port, &pp));
> >+
> >+	ocelot_rmw_gix(ocelot, 0 |
> >+		       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
> >index 000000000000..bc4dc34c684e
> >--- /dev/null
> >+++ b/drivers/net/ethernet/mscc/ocelot_police.h
> >@@ -0,0 +1,16 @@
> >+/* 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 <net/tc_act/tc_police.h>
> >+#include "ocelot.h"
> >+
> >+int ocelot_port_policer_add(struct ocelot_port *port, struct tcf_police *p);
> >+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
> >index 000000000000..97b0a7bf5d06
> >--- /dev/null
> >+++ b/drivers/net/ethernet/mscc/ocelot_tc.c
> >@@ -0,0 +1,151 @@
> >+// 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)
> >+{
> >+	const struct tc_action *a;
> >+	int err;
> >+
> >+	netdev_dbg(port->dev,
> 
> Again, no need to wrap.
> 
> 
> >+		   "%s: port %u cookie %lu\n",
> >+		   __func__, port->chip_port, f->cookie);
> >+	switch (f->command) {
> >+	case TC_CLSMATCHALL_REPLACE:
> >+		if (!tcf_exts_has_one_action(f->exts)) {
> >+			netdev_err(port->dev, "only one action is supported\n");
> >+			return -EOPNOTSUPP;
> >+		}
> >+
> >+		a = tcf_exts_first_action(f->exts);
> >+
> >+		if (is_tcf_police(a)) {
> >+			if (!ingress)
> >+				return -EOPNOTSUPP;
> >+
> >+			if (port->tc.police_id &&
> >+			    port->tc.police_id != f->cookie) {
> >+				netdev_warn(port->dev,
> 
> Again, no need to wrap.
> 
> 
> >+					    "Only one policer per port is supported\n");
> >+				return -EEXIST;
> >+			}
> >+
> >+			err = ocelot_port_policer_add(port, to_police(a));
> >+			if (err) {
> >+				netdev_err(port->dev, "Could not add policer\n");
> >+				return err;
> >+			}
> >+			port->tc.police_id = f->cookie;
> >+			return 0;
> >+		} else {
> >+			return -EOPNOTSUPP;
> >+		}
> >+	case TC_CLSMATCHALL_DESTROY:
> >+		if (port->tc.police_id != f->cookie)
> >+			return -ENOENT;
> >+
> >+		err = ocelot_port_policer_del(port);
> >+		if (err) {
> >+			netdev_err(port->dev, "Could not delete policer\n");
> >+			return err;
> >+		}
> >+		port->tc.police_id = 0;
> >+		return 0;
> >+	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,
> 
> Again, no need to wrap.
> 
> 
> >+					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,
> 
> Again, no need to wrap.
> 
> 
> >+					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;
> >+	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
> >index 000000000000..c905b98b6b4c
> >--- /dev/null
> >+++ b/drivers/net/ethernet/mscc/ocelot_tc.h
> >@@ -0,0 +1,19 @@
> >+/* 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 {
> >+	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_ */
> >-- 
> >2.17.1
> >
>
Jiri Pirko May 4, 2019, 1:07 p.m. UTC | #5
Thu, May 02, 2019 at 11:40:28AM CEST, joergen.andreasen@microchip.com wrote:
>Hardware offload of port policers are now supported via the tc command.
>Supported police parameters are: rate, burst and overhead.

Joergen, please see:
[PATCH net-next 00/13] net: act_police offload support
That patchset is also pushing flow intermediate representation for this,
so I believe that you should base this patch on top of that.
Joergen Andreasen May 7, 2019, 8:30 a.m. UTC | #6
Hi Jiri,

The 05/04/2019 15:07, Jiri Pirko wrote:
> External E-Mail
> 
> 
> Thu, May 02, 2019 at 11:40:28AM CEST, joergen.andreasen@microchip.com wrote:
> >Hardware offload of port policers are now supported via the tc command.
> >Supported police parameters are: rate, burst and overhead.
> 
> Joergen, please see:
> [PATCH net-next 00/13] net: act_police offload support
> That patchset is also pushing flow intermediate representation for this,
> so I believe that you should base this patch on top of that.
> 

I will base my patches on top of that.
diff mbox series

Patch

diff --git a/drivers/net/ethernet/mscc/Makefile b/drivers/net/ethernet/mscc/Makefile
index cb52a3b128ae..5e694dc1f7f8 100644
--- a/drivers/net/ethernet/mscc/Makefile
+++ b/drivers/net/ethernet/mscc/Makefile
@@ -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
diff --git a/drivers/net/ethernet/mscc/ocelot.c b/drivers/net/ethernet/mscc/ocelot.c
index d715ef4fc92f..3ec7864d9dc8 100644
--- a/drivers/net/ethernet/mscc/ocelot.c
+++ b/drivers/net/ethernet/mscc/ocelot.c
@@ -943,6 +943,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 +1664,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;
diff --git a/drivers/net/ethernet/mscc/ocelot.h b/drivers/net/ethernet/mscc/ocelot.h
index ba3b3380b4d0..9514979fa075 100644
--- a/drivers/net/ethernet/mscc/ocelot.h
+++ b/drivers/net/ethernet/mscc/ocelot.h
@@ -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
index 000000000000..b40382dcc748
--- /dev/null
+++ b/drivers/net/ethernet/mscc/ocelot_police.c
@@ -0,0 +1,289 @@ 
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/* Microsemi Ocelot Switch TC driver
+ *
+ * Copyright (c) 2019 Microsemi Corporation
+ */
+
+#include "ocelot_police.h"
+
+#define MSCC_RC(expr)				\
+	do {					\
+		int __rc__ = (expr);		\
+		if (__rc__ < 0)			\
+			return __rc__;		\
+	}					\
+	while (0)
+
+/* The following two functions do the same as in iproute2 */
+#define TIME_UNITS_PER_SEC	1000000
+static unsigned int tc_core_tick2time(unsigned int tick)
+{
+	return (tick * (u32)PSCHED_TICKS2NS(1)) / 1000;
+}
+
+static unsigned int tc_calc_xmitsize(u64 rate, unsigned int ticks)
+{
+	return div_u64(rate * tc_core_tick2time(ticks), TIME_UNITS_PER_SEC);
+}
+
+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,
+};
+
+/* Round x divided by y to nearest integer. x and y are integers */
+#define MSCC_ROUNDING_DIVISION(x, y) (((x) + ((y) / 2)) / (y))
+
+/* Round x divided by y to nearest higher integer. x and y are integers */
+#define MSCC_DIV_ROUND_UP(x, y) (((x) + (y) - 1) / (y))
+
+/* 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)
+{
+	struct ocelot *ocelot = port->ocelot;
+	u32 cir = 0, cbs = 0, pir = 0, pbs = 0;
+	u32 cf = 0, cir_ena = 0, frm_mode = 0;
+	u32 pbs_max = 0, cbs_max = 0;
+	bool cir_discard = 0, pir_discard = 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 = MSCC_DIV_ROUND_UP(cir, 100);
+				cir *= 3; /* 33 1/3 kbps */
+				cbs = MSCC_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 = MSCC_DIV_ROUND_UP(pir, 100);
+			pir *= 3;  /* 33 1/3 kbps */
+			pbs = MSCC_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 = MSCC_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;
+	}
+
+	/* Limit to maximum values */
+	pir = min_t(u32, GENMASK(15, 0), pir);
+	cir = min_t(u32, GENMASK(15, 0), cir);
+	pbs = min_t(u32, pbs_max, pbs);
+	cbs = min_t(u32, cbs_max, cbs);
+
+	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 tcf_police *p)
+{
+	struct ocelot *ocelot = port->ocelot;
+	struct qos_policer_conf pp;
+
+	if (!p)
+		return -EINVAL;
+
+	netdev_dbg(port->dev,
+		   "result %d ewma_rate %u burst %lld mtu %u mtu_pktoks %lld\n",
+		   p->params->tcfp_result,
+		   p->params->tcfp_ewma_rate,
+		   p->params->tcfp_burst,
+		   p->params->tcfp_mtu,
+		   p->params->tcfp_mtu_ptoks);
+
+	if (p->params->rate_present)
+		netdev_dbg(port->dev,
+			   "rate: rate %llu mult %u over %u link %u shift %u\n",
+			   p->params->rate.rate_bytes_ps,
+			   p->params->rate.mult,
+			   p->params->rate.overhead,
+			   p->params->rate.linklayer,
+			   p->params->rate.shift);
+
+	if (p->params->peak_present)
+		netdev_dbg(port->dev,
+			   "peak: rate %llu mult %u over %u link %u shift %u\n",
+			   p->params->peak.rate_bytes_ps,
+			   p->params->peak.mult,
+			   p->params->peak.overhead,
+			   p->params->peak.linklayer,
+			   p->params->peak.shift);
+
+	memset(&pp, 0, sizeof(pp));
+
+	if (p->params->tcfp_ewma_rate) {
+		netdev_err(port->dev, "tcfp_ewma_rate is not supported\n");
+		return -EOPNOTSUPP;
+	}
+	if (p->params->peak_present) {
+		netdev_err(port->dev, "peakrate is not supported\n");
+		return -EOPNOTSUPP;
+	}
+	if (!p->params->rate_present) {
+		netdev_err(port->dev, "rate not specified\n");
+		return -EINVAL;
+	}
+	if (p->params->rate.overhead) {
+		pp.mode = MSCC_QOS_RATE_MODE_LINE;
+		pp.ipg = p->params->rate.overhead;
+	} else {
+		pp.mode = MSCC_QOS_RATE_MODE_DATA;
+	}
+
+	pp.pir = (u32)div_u64(p->params->rate.rate_bytes_ps, 1000) * 8;
+	pp.pbs = tc_calc_xmitsize(p->params->rate.rate_bytes_ps,
+				  PSCHED_NS2TICKS(p->params->tcfp_burst));
+	netdev_dbg(port->dev,
+		   "%s: port %u pir %u kbps, pbs %u bytes, ipg %u bytes\n",
+		   __func__, port->chip_port, pp.pir, pp.pbs, pp.ipg);
+
+	MSCC_RC(qos_policer_conf_set(port, POL_IX_PORT + port->chip_port, &pp));
+
+	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;
+
+	netdev_dbg(port->dev, "%s: port %u\n", __func__, port->chip_port);
+
+	memset(&pp, 0, sizeof(pp));
+	pp.mode = MSCC_QOS_RATE_MODE_DISABLED;
+
+	MSCC_RC(qos_policer_conf_set(port, POL_IX_PORT + port->chip_port, &pp));
+
+	ocelot_rmw_gix(ocelot, 0 |
+		       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
index 000000000000..bc4dc34c684e
--- /dev/null
+++ b/drivers/net/ethernet/mscc/ocelot_police.h
@@ -0,0 +1,16 @@ 
+/* 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 <net/tc_act/tc_police.h>
+#include "ocelot.h"
+
+int ocelot_port_policer_add(struct ocelot_port *port, struct tcf_police *p);
+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
index 000000000000..97b0a7bf5d06
--- /dev/null
+++ b/drivers/net/ethernet/mscc/ocelot_tc.c
@@ -0,0 +1,151 @@ 
+// 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)
+{
+	const struct tc_action *a;
+	int err;
+
+	netdev_dbg(port->dev,
+		   "%s: port %u cookie %lu\n",
+		   __func__, port->chip_port, f->cookie);
+	switch (f->command) {
+	case TC_CLSMATCHALL_REPLACE:
+		if (!tcf_exts_has_one_action(f->exts)) {
+			netdev_err(port->dev, "only one action is supported\n");
+			return -EOPNOTSUPP;
+		}
+
+		a = tcf_exts_first_action(f->exts);
+
+		if (is_tcf_police(a)) {
+			if (!ingress)
+				return -EOPNOTSUPP;
+
+			if (port->tc.police_id &&
+			    port->tc.police_id != f->cookie) {
+				netdev_warn(port->dev,
+					    "Only one policer per port is supported\n");
+				return -EEXIST;
+			}
+
+			err = ocelot_port_policer_add(port, to_police(a));
+			if (err) {
+				netdev_err(port->dev, "Could not add policer\n");
+				return err;
+			}
+			port->tc.police_id = f->cookie;
+			return 0;
+		} else {
+			return -EOPNOTSUPP;
+		}
+	case TC_CLSMATCHALL_DESTROY:
+		if (port->tc.police_id != f->cookie)
+			return -ENOENT;
+
+		err = ocelot_port_policer_del(port);
+		if (err) {
+			netdev_err(port->dev, "Could not delete policer\n");
+			return err;
+		}
+		port->tc.police_id = 0;
+		return 0;
+	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;
+	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
index 000000000000..c905b98b6b4c
--- /dev/null
+++ b/drivers/net/ethernet/mscc/ocelot_tc.h
@@ -0,0 +1,19 @@ 
+/* 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 {
+	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_ */