root/drivers/net/ethernet/mscc/ocelot_police.c
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/* Microsemi Ocelot Switch driver
 *
 * Copyright (c) 2019 Microsemi Corporation
 */

#include <soc/mscc/ocelot.h>
#include "ocelot_police.h"

/* 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) */

int qos_policer_conf_set(struct ocelot *ocelot, 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;
        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)) {
                dev_err(ocelot->dev,
                        "Invalid pir for policer %u: %u (max %lu)\n",
                        pol_ix, pir, GENMASK(15, 0));
                return -EINVAL;
        }

        if (cir > GENMASK(15, 0)) {
                dev_err(ocelot->dev,
                        "Invalid cir for policer %u: %u (max %lu)\n",
                        pol_ix, cir, GENMASK(15, 0));
                return -EINVAL;
        }

        if (pbs > pbs_max) {
                dev_err(ocelot->dev,
                        "Invalid pbs for policer %u: %u (max %u)\n",
                        pol_ix, pbs, pbs_max);
                return -EINVAL;
        }

        if (cbs > cbs_max) {
                dev_err(ocelot->dev,
                        "Invalid cbs for policer %u: %u (max %u)\n",
                        pol_ix, cbs, cbs_max);
                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_policer_validate(const struct flow_action *action,
                            const struct flow_action_entry *a,
                            struct netlink_ext_ack *extack)
{
        if (a->police.exceed.act_id != FLOW_ACTION_DROP) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Offload not supported when exceed action is not drop");
                return -EOPNOTSUPP;
        }

        if (a->police.notexceed.act_id != FLOW_ACTION_PIPE &&
            a->police.notexceed.act_id != FLOW_ACTION_ACCEPT) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Offload not supported when conform action is not pipe or ok");
                return -EOPNOTSUPP;
        }

        if (a->police.notexceed.act_id == FLOW_ACTION_ACCEPT &&
            !flow_action_is_last_entry(action, a)) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Offload not supported when conform action is ok, but police action is not last");
                return -EOPNOTSUPP;
        }

        if (a->police.peakrate_bytes_ps ||
            a->police.avrate || a->police.overhead) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Offload not supported when peakrate/avrate/overhead is configured");
                return -EOPNOTSUPP;
        }

        if (a->police.rate_pkt_ps) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Offload does not support packets per second");
                return -EOPNOTSUPP;
        }

        return 0;
}
EXPORT_SYMBOL(ocelot_policer_validate);

int ocelot_port_policer_add(struct ocelot *ocelot, int port,
                            struct ocelot_policer *pol)
{
        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;

        dev_dbg(ocelot->dev, "%s: port %u pir %u kbps, pbs %u bytes\n",
                __func__, port, pp.pir, pp.pbs);

        err = qos_policer_conf_set(ocelot, POL_IX_PORT + 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);

        return 0;
}
EXPORT_SYMBOL(ocelot_port_policer_add);

int ocelot_port_policer_del(struct ocelot *ocelot, int port)
{
        struct qos_policer_conf pp = { 0 };
        int err;

        dev_dbg(ocelot->dev, "%s: port %u\n", __func__, port);

        pp.mode = MSCC_QOS_RATE_MODE_DISABLED;

        err = qos_policer_conf_set(ocelot, POL_IX_PORT + 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);

        return 0;
}
EXPORT_SYMBOL(ocelot_port_policer_del);