xref: /openbmc/linux/drivers/net/ethernet/mscc/ocelot_police.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
12c1d029aSJoergen Andreasen // SPDX-License-Identifier: (GPL-2.0 OR MIT)
22c1d029aSJoergen Andreasen /* Microsemi Ocelot Switch driver
32c1d029aSJoergen Andreasen  *
42c1d029aSJoergen Andreasen  * Copyright (c) 2019 Microsemi Corporation
52c1d029aSJoergen Andreasen  */
62c1d029aSJoergen Andreasen 
7fc411eaaSVladimir Oltean #include <soc/mscc/ocelot.h>
82c1d029aSJoergen Andreasen #include "ocelot_police.h"
92c1d029aSJoergen Andreasen 
102c1d029aSJoergen Andreasen /* Types for ANA:POL[0-192]:POL_MODE_CFG.FRM_MODE */
112c1d029aSJoergen Andreasen #define POL_MODE_LINERATE   0 /* Incl IPG. Unit: 33 1/3 kbps, 4096 bytes */
122c1d029aSJoergen Andreasen #define POL_MODE_DATARATE   1 /* Excl IPG. Unit: 33 1/3 kbps, 4096 bytes  */
132c1d029aSJoergen Andreasen #define POL_MODE_FRMRATE_HI 2 /* Unit: 33 1/3 fps, 32.8 frames */
142c1d029aSJoergen Andreasen #define POL_MODE_FRMRATE_LO 3 /* Unit: 1/3 fps, 0.3 frames */
152c1d029aSJoergen Andreasen 
162c1d029aSJoergen Andreasen /* Policer indexes */
172c1d029aSJoergen Andreasen #define POL_IX_PORT    0    /* 0-11    : Port policers */
182c1d029aSJoergen Andreasen #define POL_IX_QUEUE   32   /* 32-127  : Queue policers  */
192c1d029aSJoergen Andreasen 
202c1d029aSJoergen Andreasen /* Default policer order */
212c1d029aSJoergen Andreasen #define POL_ORDER 0x1d3 /* Ocelot policer order: Serial (QoS -> Port -> VCAP) */
222c1d029aSJoergen Andreasen 
qos_policer_conf_set(struct ocelot * ocelot,u32 pol_ix,struct qos_policer_conf * conf)23*8e90c499SVladimir Oltean int qos_policer_conf_set(struct ocelot *ocelot, u32 pol_ix,
242c1d029aSJoergen Andreasen 			 struct qos_policer_conf *conf)
252c1d029aSJoergen Andreasen {
262c1d029aSJoergen Andreasen 	u32 cf = 0, cir_ena = 0, frm_mode = POL_MODE_LINERATE;
272c1d029aSJoergen Andreasen 	u32 cir = 0, cbs = 0, pir = 0, pbs = 0;
282c1d029aSJoergen Andreasen 	bool cir_discard = 0, pir_discard = 0;
292c1d029aSJoergen Andreasen 	u32 pbs_max = 0, cbs_max = 0;
302c1d029aSJoergen Andreasen 	u8 ipg = 20;
312c1d029aSJoergen Andreasen 	u32 value;
322c1d029aSJoergen Andreasen 
332c1d029aSJoergen Andreasen 	pir = conf->pir;
342c1d029aSJoergen Andreasen 	pbs = conf->pbs;
352c1d029aSJoergen Andreasen 
362c1d029aSJoergen Andreasen 	switch (conf->mode) {
372c1d029aSJoergen Andreasen 	case MSCC_QOS_RATE_MODE_LINE:
382c1d029aSJoergen Andreasen 	case MSCC_QOS_RATE_MODE_DATA:
392c1d029aSJoergen Andreasen 		if (conf->mode == MSCC_QOS_RATE_MODE_LINE) {
402c1d029aSJoergen Andreasen 			frm_mode = POL_MODE_LINERATE;
412c1d029aSJoergen Andreasen 			ipg = min_t(u8, GENMASK(4, 0), conf->ipg);
422c1d029aSJoergen Andreasen 		} else {
432c1d029aSJoergen Andreasen 			frm_mode = POL_MODE_DATARATE;
442c1d029aSJoergen Andreasen 		}
452c1d029aSJoergen Andreasen 		if (conf->dlb) {
462c1d029aSJoergen Andreasen 			cir_ena = 1;
472c1d029aSJoergen Andreasen 			cir = conf->cir;
482c1d029aSJoergen Andreasen 			cbs = conf->cbs;
492c1d029aSJoergen Andreasen 			if (cir == 0 && cbs == 0) {
502c1d029aSJoergen Andreasen 				/* Discard cir frames */
512c1d029aSJoergen Andreasen 				cir_discard = 1;
522c1d029aSJoergen Andreasen 			} else {
532c1d029aSJoergen Andreasen 				cir = DIV_ROUND_UP(cir, 100);
542c1d029aSJoergen Andreasen 				cir *= 3; /* 33 1/3 kbps */
552c1d029aSJoergen Andreasen 				cbs = DIV_ROUND_UP(cbs, 4096);
562c1d029aSJoergen Andreasen 				cbs = (cbs ? cbs : 1); /* No zero burst size */
572c1d029aSJoergen Andreasen 				cbs_max = 60; /* Limit burst size */
582c1d029aSJoergen Andreasen 				cf = conf->cf;
592c1d029aSJoergen Andreasen 				if (cf)
602c1d029aSJoergen Andreasen 					pir += conf->cir;
612c1d029aSJoergen Andreasen 			}
622c1d029aSJoergen Andreasen 		}
632c1d029aSJoergen Andreasen 		if (pir == 0 && pbs == 0) {
642c1d029aSJoergen Andreasen 			/* Discard PIR frames */
652c1d029aSJoergen Andreasen 			pir_discard = 1;
662c1d029aSJoergen Andreasen 		} else {
672c1d029aSJoergen Andreasen 			pir = DIV_ROUND_UP(pir, 100);
682c1d029aSJoergen Andreasen 			pir *= 3;  /* 33 1/3 kbps */
692c1d029aSJoergen Andreasen 			pbs = DIV_ROUND_UP(pbs, 4096);
702c1d029aSJoergen Andreasen 			pbs = (pbs ? pbs : 1); /* No zero burst size */
712c1d029aSJoergen Andreasen 			pbs_max = 60; /* Limit burst size */
722c1d029aSJoergen Andreasen 		}
732c1d029aSJoergen Andreasen 		break;
742c1d029aSJoergen Andreasen 	case MSCC_QOS_RATE_MODE_FRAME:
752c1d029aSJoergen Andreasen 		if (pir >= 100) {
762c1d029aSJoergen Andreasen 			frm_mode = POL_MODE_FRMRATE_HI;
772c1d029aSJoergen Andreasen 			pir = DIV_ROUND_UP(pir, 100);
782c1d029aSJoergen Andreasen 			pir *= 3;  /* 33 1/3 fps */
792c1d029aSJoergen Andreasen 			pbs = (pbs * 10) / 328; /* 32.8 frames */
802c1d029aSJoergen Andreasen 			pbs = (pbs ? pbs : 1); /* No zero burst size */
812c1d029aSJoergen Andreasen 			pbs_max = GENMASK(6, 0); /* Limit burst size */
822c1d029aSJoergen Andreasen 		} else {
832c1d029aSJoergen Andreasen 			frm_mode = POL_MODE_FRMRATE_LO;
842c1d029aSJoergen Andreasen 			if (pir == 0 && pbs == 0) {
852c1d029aSJoergen Andreasen 				/* Discard all frames */
862c1d029aSJoergen Andreasen 				pir_discard = 1;
872c1d029aSJoergen Andreasen 				cir_discard = 1;
882c1d029aSJoergen Andreasen 			} else {
892c1d029aSJoergen Andreasen 				pir *= 3; /* 1/3 fps */
902c1d029aSJoergen Andreasen 				pbs = (pbs * 10) / 3; /* 0.3 frames */
912c1d029aSJoergen Andreasen 				pbs = (pbs ? pbs : 1); /* No zero burst size */
922c1d029aSJoergen Andreasen 				pbs_max = 61; /* Limit burst size */
932c1d029aSJoergen Andreasen 			}
942c1d029aSJoergen Andreasen 		}
952c1d029aSJoergen Andreasen 		break;
962c1d029aSJoergen Andreasen 	default: /* MSCC_QOS_RATE_MODE_DISABLED */
972c1d029aSJoergen Andreasen 		/* Disable policer using maximum rate and zero burst */
982c1d029aSJoergen Andreasen 		pir = GENMASK(15, 0);
992c1d029aSJoergen Andreasen 		pbs = 0;
1002c1d029aSJoergen Andreasen 		break;
1012c1d029aSJoergen Andreasen 	}
1022c1d029aSJoergen Andreasen 
1032c1d029aSJoergen Andreasen 	/* Check limits */
1042c1d029aSJoergen Andreasen 	if (pir > GENMASK(15, 0)) {
105*8e90c499SVladimir Oltean 		dev_err(ocelot->dev,
106*8e90c499SVladimir Oltean 			"Invalid pir for policer %u: %u (max %lu)\n",
107*8e90c499SVladimir Oltean 			pol_ix, pir, GENMASK(15, 0));
1082c1d029aSJoergen Andreasen 		return -EINVAL;
1092c1d029aSJoergen Andreasen 	}
1102c1d029aSJoergen Andreasen 
1112c1d029aSJoergen Andreasen 	if (cir > GENMASK(15, 0)) {
112*8e90c499SVladimir Oltean 		dev_err(ocelot->dev,
113*8e90c499SVladimir Oltean 			"Invalid cir for policer %u: %u (max %lu)\n",
114*8e90c499SVladimir Oltean 			pol_ix, cir, GENMASK(15, 0));
1152c1d029aSJoergen Andreasen 		return -EINVAL;
1162c1d029aSJoergen Andreasen 	}
1172c1d029aSJoergen Andreasen 
1182c1d029aSJoergen Andreasen 	if (pbs > pbs_max) {
119*8e90c499SVladimir Oltean 		dev_err(ocelot->dev,
120*8e90c499SVladimir Oltean 			"Invalid pbs for policer %u: %u (max %u)\n",
121*8e90c499SVladimir Oltean 			pol_ix, pbs, pbs_max);
1222c1d029aSJoergen Andreasen 		return -EINVAL;
1232c1d029aSJoergen Andreasen 	}
1242c1d029aSJoergen Andreasen 
1252c1d029aSJoergen Andreasen 	if (cbs > cbs_max) {
126*8e90c499SVladimir Oltean 		dev_err(ocelot->dev,
127*8e90c499SVladimir Oltean 			"Invalid cbs for policer %u: %u (max %u)\n",
128*8e90c499SVladimir Oltean 			pol_ix, cbs, cbs_max);
1292c1d029aSJoergen Andreasen 		return -EINVAL;
1302c1d029aSJoergen Andreasen 	}
1312c1d029aSJoergen Andreasen 
1322c1d029aSJoergen Andreasen 	value = (ANA_POL_MODE_CFG_IPG_SIZE(ipg) |
1332c1d029aSJoergen Andreasen 		 ANA_POL_MODE_CFG_FRM_MODE(frm_mode) |
1342c1d029aSJoergen Andreasen 		 (cf ? ANA_POL_MODE_CFG_DLB_COUPLED : 0) |
1352c1d029aSJoergen Andreasen 		 (cir_ena ? ANA_POL_MODE_CFG_CIR_ENA : 0) |
1362c1d029aSJoergen Andreasen 		 ANA_POL_MODE_CFG_OVERSHOOT_ENA);
1372c1d029aSJoergen Andreasen 
1382c1d029aSJoergen Andreasen 	ocelot_write_gix(ocelot, value, ANA_POL_MODE_CFG, pol_ix);
1392c1d029aSJoergen Andreasen 
1402c1d029aSJoergen Andreasen 	ocelot_write_gix(ocelot,
1412c1d029aSJoergen Andreasen 			 ANA_POL_PIR_CFG_PIR_RATE(pir) |
1422c1d029aSJoergen Andreasen 			 ANA_POL_PIR_CFG_PIR_BURST(pbs),
1432c1d029aSJoergen Andreasen 			 ANA_POL_PIR_CFG, pol_ix);
1442c1d029aSJoergen Andreasen 
1452c1d029aSJoergen Andreasen 	ocelot_write_gix(ocelot,
1462c1d029aSJoergen Andreasen 			 (pir_discard ? GENMASK(22, 0) : 0),
1472c1d029aSJoergen Andreasen 			 ANA_POL_PIR_STATE, pol_ix);
1482c1d029aSJoergen Andreasen 
1492c1d029aSJoergen Andreasen 	ocelot_write_gix(ocelot,
1502c1d029aSJoergen Andreasen 			 ANA_POL_CIR_CFG_CIR_RATE(cir) |
1512c1d029aSJoergen Andreasen 			 ANA_POL_CIR_CFG_CIR_BURST(cbs),
1522c1d029aSJoergen Andreasen 			 ANA_POL_CIR_CFG, pol_ix);
1532c1d029aSJoergen Andreasen 
1542c1d029aSJoergen Andreasen 	ocelot_write_gix(ocelot,
1552c1d029aSJoergen Andreasen 			 (cir_discard ? GENMASK(22, 0) : 0),
1562c1d029aSJoergen Andreasen 			 ANA_POL_CIR_STATE, pol_ix);
1572c1d029aSJoergen Andreasen 
1582c1d029aSJoergen Andreasen 	return 0;
1592c1d029aSJoergen Andreasen }
1602c1d029aSJoergen Andreasen 
ocelot_policer_validate(const struct flow_action * action,const struct flow_action_entry * a,struct netlink_ext_ack * extack)161d97b4b10SJianbo Liu int ocelot_policer_validate(const struct flow_action *action,
162d97b4b10SJianbo Liu 			    const struct flow_action_entry *a,
163d97b4b10SJianbo Liu 			    struct netlink_ext_ack *extack)
164d97b4b10SJianbo Liu {
165d97b4b10SJianbo Liu 	if (a->police.exceed.act_id != FLOW_ACTION_DROP) {
166d97b4b10SJianbo Liu 		NL_SET_ERR_MSG_MOD(extack,
167d97b4b10SJianbo Liu 				   "Offload not supported when exceed action is not drop");
168d97b4b10SJianbo Liu 		return -EOPNOTSUPP;
169d97b4b10SJianbo Liu 	}
170d97b4b10SJianbo Liu 
171d97b4b10SJianbo Liu 	if (a->police.notexceed.act_id != FLOW_ACTION_PIPE &&
172d97b4b10SJianbo Liu 	    a->police.notexceed.act_id != FLOW_ACTION_ACCEPT) {
173d97b4b10SJianbo Liu 		NL_SET_ERR_MSG_MOD(extack,
174d97b4b10SJianbo Liu 				   "Offload not supported when conform action is not pipe or ok");
175d97b4b10SJianbo Liu 		return -EOPNOTSUPP;
176d97b4b10SJianbo Liu 	}
177d97b4b10SJianbo Liu 
178d97b4b10SJianbo Liu 	if (a->police.notexceed.act_id == FLOW_ACTION_ACCEPT &&
179d97b4b10SJianbo Liu 	    !flow_action_is_last_entry(action, a)) {
180d97b4b10SJianbo Liu 		NL_SET_ERR_MSG_MOD(extack,
181d97b4b10SJianbo Liu 				   "Offload not supported when conform action is ok, but police action is not last");
182d97b4b10SJianbo Liu 		return -EOPNOTSUPP;
183d97b4b10SJianbo Liu 	}
184d97b4b10SJianbo Liu 
185d97b4b10SJianbo Liu 	if (a->police.peakrate_bytes_ps ||
186d97b4b10SJianbo Liu 	    a->police.avrate || a->police.overhead) {
187d97b4b10SJianbo Liu 		NL_SET_ERR_MSG_MOD(extack,
188d97b4b10SJianbo Liu 				   "Offload not supported when peakrate/avrate/overhead is configured");
189d97b4b10SJianbo Liu 		return -EOPNOTSUPP;
190d97b4b10SJianbo Liu 	}
191d97b4b10SJianbo Liu 
192d97b4b10SJianbo Liu 	if (a->police.rate_pkt_ps) {
193d97b4b10SJianbo Liu 		NL_SET_ERR_MSG_MOD(extack,
194d97b4b10SJianbo Liu 				   "Offload does not support packets per second");
195d97b4b10SJianbo Liu 		return -EOPNOTSUPP;
196d97b4b10SJianbo Liu 	}
197d97b4b10SJianbo Liu 
198d97b4b10SJianbo Liu 	return 0;
199d97b4b10SJianbo Liu }
200d97b4b10SJianbo Liu EXPORT_SYMBOL(ocelot_policer_validate);
201d97b4b10SJianbo Liu 
ocelot_port_policer_add(struct ocelot * ocelot,int port,struct ocelot_policer * pol)202f270dbfaSVladimir Oltean int ocelot_port_policer_add(struct ocelot *ocelot, int port,
2032c1d029aSJoergen Andreasen 			    struct ocelot_policer *pol)
2042c1d029aSJoergen Andreasen {
2052c1d029aSJoergen Andreasen 	struct qos_policer_conf pp = { 0 };
2062c1d029aSJoergen Andreasen 	int err;
2072c1d029aSJoergen Andreasen 
2082c1d029aSJoergen Andreasen 	if (!pol)
2092c1d029aSJoergen Andreasen 		return -EINVAL;
2102c1d029aSJoergen Andreasen 
2112c1d029aSJoergen Andreasen 	pp.mode = MSCC_QOS_RATE_MODE_DATA;
2122c1d029aSJoergen Andreasen 	pp.pir = pol->rate;
2132c1d029aSJoergen Andreasen 	pp.pbs = pol->burst;
2142c1d029aSJoergen Andreasen 
215f270dbfaSVladimir Oltean 	dev_dbg(ocelot->dev, "%s: port %u pir %u kbps, pbs %u bytes\n",
216f270dbfaSVladimir Oltean 		__func__, port, pp.pir, pp.pbs);
2172c1d029aSJoergen Andreasen 
218*8e90c499SVladimir Oltean 	err = qos_policer_conf_set(ocelot, POL_IX_PORT + port, &pp);
2192c1d029aSJoergen Andreasen 	if (err)
2202c1d029aSJoergen Andreasen 		return err;
2212c1d029aSJoergen Andreasen 
2222c1d029aSJoergen Andreasen 	ocelot_rmw_gix(ocelot,
2232c1d029aSJoergen Andreasen 		       ANA_PORT_POL_CFG_PORT_POL_ENA |
2242c1d029aSJoergen Andreasen 		       ANA_PORT_POL_CFG_POL_ORDER(POL_ORDER),
2252c1d029aSJoergen Andreasen 		       ANA_PORT_POL_CFG_PORT_POL_ENA |
2262c1d029aSJoergen Andreasen 		       ANA_PORT_POL_CFG_POL_ORDER_M,
227f270dbfaSVladimir Oltean 		       ANA_PORT_POL_CFG, port);
2282c1d029aSJoergen Andreasen 
2292c1d029aSJoergen Andreasen 	return 0;
2302c1d029aSJoergen Andreasen }
231fc411eaaSVladimir Oltean EXPORT_SYMBOL(ocelot_port_policer_add);
2322c1d029aSJoergen Andreasen 
ocelot_port_policer_del(struct ocelot * ocelot,int port)233f270dbfaSVladimir Oltean int ocelot_port_policer_del(struct ocelot *ocelot, int port)
2342c1d029aSJoergen Andreasen {
2352c1d029aSJoergen Andreasen 	struct qos_policer_conf pp = { 0 };
2362c1d029aSJoergen Andreasen 	int err;
2372c1d029aSJoergen Andreasen 
238f270dbfaSVladimir Oltean 	dev_dbg(ocelot->dev, "%s: port %u\n", __func__, port);
2392c1d029aSJoergen Andreasen 
2402c1d029aSJoergen Andreasen 	pp.mode = MSCC_QOS_RATE_MODE_DISABLED;
2412c1d029aSJoergen Andreasen 
242*8e90c499SVladimir Oltean 	err = qos_policer_conf_set(ocelot, POL_IX_PORT + port, &pp);
2432c1d029aSJoergen Andreasen 	if (err)
2442c1d029aSJoergen Andreasen 		return err;
2452c1d029aSJoergen Andreasen 
2462c1d029aSJoergen Andreasen 	ocelot_rmw_gix(ocelot,
2472c1d029aSJoergen Andreasen 		       ANA_PORT_POL_CFG_POL_ORDER(POL_ORDER),
2482c1d029aSJoergen Andreasen 		       ANA_PORT_POL_CFG_PORT_POL_ENA |
2492c1d029aSJoergen Andreasen 		       ANA_PORT_POL_CFG_POL_ORDER_M,
250f270dbfaSVladimir Oltean 		       ANA_PORT_POL_CFG, port);
2512c1d029aSJoergen Andreasen 
2522c1d029aSJoergen Andreasen 	return 0;
2532c1d029aSJoergen Andreasen }
254fc411eaaSVladimir Oltean EXPORT_SYMBOL(ocelot_port_policer_del);
255