408 lines
10 KiB
C
408 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/* Microchip Sparx5 Switch driver
|
|
*
|
|
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
|
|
*/
|
|
|
|
#include <net/dcbnl.h>
|
|
|
|
#include "sparx5_port.h"
|
|
|
|
enum sparx5_dcb_apptrust_values {
|
|
SPARX5_DCB_APPTRUST_EMPTY,
|
|
SPARX5_DCB_APPTRUST_DSCP,
|
|
SPARX5_DCB_APPTRUST_PCP,
|
|
SPARX5_DCB_APPTRUST_DSCP_PCP,
|
|
__SPARX5_DCB_APPTRUST_MAX
|
|
};
|
|
|
|
static const struct sparx5_dcb_apptrust {
|
|
u8 selectors[IEEE_8021QAZ_APP_SEL_MAX + 1];
|
|
int nselectors;
|
|
} *sparx5_port_apptrust[SPX5_PORTS];
|
|
|
|
static const char *sparx5_dcb_apptrust_names[__SPARX5_DCB_APPTRUST_MAX] = {
|
|
[SPARX5_DCB_APPTRUST_EMPTY] = "empty",
|
|
[SPARX5_DCB_APPTRUST_DSCP] = "dscp",
|
|
[SPARX5_DCB_APPTRUST_PCP] = "pcp",
|
|
[SPARX5_DCB_APPTRUST_DSCP_PCP] = "dscp pcp"
|
|
};
|
|
|
|
/* Sparx5 supported apptrust policies */
|
|
static const struct sparx5_dcb_apptrust
|
|
sparx5_dcb_apptrust_policies[__SPARX5_DCB_APPTRUST_MAX] = {
|
|
/* Empty *must* be first */
|
|
[SPARX5_DCB_APPTRUST_EMPTY] = { { 0 }, 0 },
|
|
[SPARX5_DCB_APPTRUST_DSCP] = { { IEEE_8021QAZ_APP_SEL_DSCP }, 1 },
|
|
[SPARX5_DCB_APPTRUST_PCP] = { { DCB_APP_SEL_PCP }, 1 },
|
|
[SPARX5_DCB_APPTRUST_DSCP_PCP] = { { IEEE_8021QAZ_APP_SEL_DSCP,
|
|
DCB_APP_SEL_PCP }, 2 },
|
|
};
|
|
|
|
/* Validate app entry.
|
|
*
|
|
* Check for valid selectors and valid protocol and priority ranges.
|
|
*/
|
|
static int sparx5_dcb_app_validate(struct net_device *dev,
|
|
const struct dcb_app *app)
|
|
{
|
|
int err = 0;
|
|
|
|
switch (app->selector) {
|
|
/* Default priority checks */
|
|
case IEEE_8021QAZ_APP_SEL_ETHERTYPE:
|
|
if (app->protocol != 0)
|
|
err = -EINVAL;
|
|
else if (app->priority >= SPX5_PRIOS)
|
|
err = -ERANGE;
|
|
break;
|
|
/* Dscp checks */
|
|
case IEEE_8021QAZ_APP_SEL_DSCP:
|
|
if (app->protocol >= SPARX5_PORT_QOS_DSCP_COUNT)
|
|
err = -EINVAL;
|
|
else if (app->priority >= SPX5_PRIOS)
|
|
err = -ERANGE;
|
|
break;
|
|
/* Pcp checks */
|
|
case DCB_APP_SEL_PCP:
|
|
if (app->protocol >= SPARX5_PORT_QOS_PCP_DEI_COUNT)
|
|
err = -EINVAL;
|
|
else if (app->priority >= SPX5_PRIOS)
|
|
err = -ERANGE;
|
|
break;
|
|
default:
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (err)
|
|
netdev_err(dev, "Invalid entry: %d:%d\n", app->protocol,
|
|
app->priority);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Validate apptrust configuration.
|
|
*
|
|
* Return index of supported apptrust configuration if valid, otherwise return
|
|
* error.
|
|
*/
|
|
static int sparx5_dcb_apptrust_validate(struct net_device *dev, u8 *selectors,
|
|
int nselectors, int *err)
|
|
{
|
|
bool match = false;
|
|
int i, ii;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sparx5_dcb_apptrust_policies); i++) {
|
|
if (sparx5_dcb_apptrust_policies[i].nselectors != nselectors)
|
|
continue;
|
|
match = true;
|
|
for (ii = 0; ii < nselectors; ii++) {
|
|
if (sparx5_dcb_apptrust_policies[i].selectors[ii] !=
|
|
*(selectors + ii)) {
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
if (match)
|
|
break;
|
|
}
|
|
|
|
/* Requested trust configuration is not supported */
|
|
if (!match) {
|
|
netdev_err(dev, "Valid apptrust configurations are:\n");
|
|
for (i = 0; i < ARRAY_SIZE(sparx5_dcb_apptrust_names); i++)
|
|
pr_info("order: %s\n", sparx5_dcb_apptrust_names[i]);
|
|
*err = -EOPNOTSUPP;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static bool sparx5_dcb_apptrust_contains(int portno, u8 selector)
|
|
{
|
|
const struct sparx5_dcb_apptrust *conf = sparx5_port_apptrust[portno];
|
|
int i;
|
|
|
|
for (i = 0; i < conf->nselectors; i++)
|
|
if (conf->selectors[i] == selector)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int sparx5_dcb_app_update(struct net_device *dev)
|
|
{
|
|
struct dcb_ieee_app_prio_map dscp_rewr_map = {0};
|
|
struct dcb_rewr_prio_pcp_map pcp_rewr_map = {0};
|
|
struct sparx5_port *port = netdev_priv(dev);
|
|
struct sparx5_port_qos_dscp_map *dscp_map;
|
|
struct sparx5_port_qos_pcp_map *pcp_map;
|
|
struct sparx5_port_qos qos = {0};
|
|
struct dcb_app app_itr = {0};
|
|
int portno = port->portno;
|
|
bool dscp_rewr = false;
|
|
bool pcp_rewr = false;
|
|
u16 dscp;
|
|
int i;
|
|
|
|
dscp_map = &qos.dscp.map;
|
|
pcp_map = &qos.pcp.map;
|
|
|
|
/* Get default prio. */
|
|
qos.default_prio = dcb_ieee_getapp_default_prio_mask(dev);
|
|
if (qos.default_prio)
|
|
qos.default_prio = fls(qos.default_prio) - 1;
|
|
|
|
/* Get dscp ingress mapping */
|
|
for (i = 0; i < ARRAY_SIZE(dscp_map->map); i++) {
|
|
app_itr.selector = IEEE_8021QAZ_APP_SEL_DSCP;
|
|
app_itr.protocol = i;
|
|
dscp_map->map[i] = dcb_getapp(dev, &app_itr);
|
|
}
|
|
|
|
/* Get pcp ingress mapping */
|
|
for (i = 0; i < ARRAY_SIZE(pcp_map->map); i++) {
|
|
app_itr.selector = DCB_APP_SEL_PCP;
|
|
app_itr.protocol = i;
|
|
pcp_map->map[i] = dcb_getapp(dev, &app_itr);
|
|
}
|
|
|
|
/* Get pcp rewrite mapping */
|
|
dcb_getrewr_prio_pcp_mask_map(dev, &pcp_rewr_map);
|
|
for (i = 0; i < ARRAY_SIZE(pcp_rewr_map.map); i++) {
|
|
if (!pcp_rewr_map.map[i])
|
|
continue;
|
|
pcp_rewr = true;
|
|
qos.pcp_rewr.map.map[i] = fls(pcp_rewr_map.map[i]) - 1;
|
|
}
|
|
|
|
/* Get dscp rewrite mapping */
|
|
dcb_getrewr_prio_dscp_mask_map(dev, &dscp_rewr_map);
|
|
for (i = 0; i < ARRAY_SIZE(dscp_rewr_map.map); i++) {
|
|
if (!dscp_rewr_map.map[i])
|
|
continue;
|
|
|
|
/* The rewrite table of the switch has 32 entries; one for each
|
|
* priority for each DP level. Currently, the rewrite map does
|
|
* not indicate DP level, so we map classified QoS class to
|
|
* classified DSCP, for each classified DP level. Rewrite of
|
|
* DSCP is only enabled, if we have active mappings.
|
|
*/
|
|
dscp_rewr = true;
|
|
dscp = fls64(dscp_rewr_map.map[i]) - 1;
|
|
qos.dscp_rewr.map.map[i] = dscp; /* DP 0 */
|
|
qos.dscp_rewr.map.map[i + 8] = dscp; /* DP 1 */
|
|
qos.dscp_rewr.map.map[i + 16] = dscp; /* DP 2 */
|
|
qos.dscp_rewr.map.map[i + 24] = dscp; /* DP 3 */
|
|
}
|
|
|
|
/* Enable use of pcp for queue classification ? */
|
|
if (sparx5_dcb_apptrust_contains(portno, DCB_APP_SEL_PCP)) {
|
|
qos.pcp.qos_enable = true;
|
|
qos.pcp.dp_enable = qos.pcp.qos_enable;
|
|
/* Enable rewrite of PCP and DEI if PCP is trusted *and* rewrite
|
|
* table is not empty.
|
|
*/
|
|
if (pcp_rewr)
|
|
qos.pcp_rewr.enable = true;
|
|
}
|
|
|
|
/* Enable use of dscp for queue classification ? */
|
|
if (sparx5_dcb_apptrust_contains(portno, IEEE_8021QAZ_APP_SEL_DSCP)) {
|
|
qos.dscp.qos_enable = true;
|
|
qos.dscp.dp_enable = qos.dscp.qos_enable;
|
|
if (dscp_rewr)
|
|
/* Do not enable rewrite if no mappings are active, as
|
|
* classified DSCP will then be zero for all classified
|
|
* QoS class and DP combinations.
|
|
*/
|
|
qos.dscp_rewr.enable = true;
|
|
}
|
|
|
|
return sparx5_port_qos_set(port, &qos);
|
|
}
|
|
|
|
/* Set or delete DSCP app entry.
|
|
*
|
|
* DSCP mapping is global for all ports, so set and delete app entries are
|
|
* replicated for each port.
|
|
*/
|
|
static int sparx5_dcb_ieee_dscp_setdel(struct net_device *dev,
|
|
struct dcb_app *app,
|
|
int (*setdel)(struct net_device *,
|
|
struct dcb_app *))
|
|
{
|
|
struct sparx5_port *port = netdev_priv(dev);
|
|
struct sparx5_port *port_itr;
|
|
int err, i;
|
|
|
|
for (i = 0; i < SPX5_PORTS; i++) {
|
|
port_itr = port->sparx5->ports[i];
|
|
if (!port_itr)
|
|
continue;
|
|
err = setdel(port_itr->ndev, app);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sparx5_dcb_ieee_delapp(struct net_device *dev, struct dcb_app *app)
|
|
{
|
|
int err;
|
|
|
|
if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
|
|
err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_ieee_delapp);
|
|
else
|
|
err = dcb_ieee_delapp(dev, app);
|
|
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return sparx5_dcb_app_update(dev);
|
|
}
|
|
|
|
static int sparx5_dcb_ieee_setapp(struct net_device *dev, struct dcb_app *app)
|
|
{
|
|
struct dcb_app app_itr;
|
|
int err = 0;
|
|
u8 prio;
|
|
|
|
err = sparx5_dcb_app_validate(dev, app);
|
|
if (err)
|
|
goto out;
|
|
|
|
/* Delete current mapping, if it exists */
|
|
prio = dcb_getapp(dev, app);
|
|
if (prio) {
|
|
app_itr = *app;
|
|
app_itr.priority = prio;
|
|
sparx5_dcb_ieee_delapp(dev, &app_itr);
|
|
}
|
|
|
|
if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
|
|
err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_ieee_setapp);
|
|
else
|
|
err = dcb_ieee_setapp(dev, app);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
sparx5_dcb_app_update(dev);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int sparx5_dcb_setapptrust(struct net_device *dev, u8 *selectors,
|
|
int nselectors)
|
|
{
|
|
struct sparx5_port *port = netdev_priv(dev);
|
|
int err = 0, idx;
|
|
|
|
idx = sparx5_dcb_apptrust_validate(dev, selectors, nselectors, &err);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
sparx5_port_apptrust[port->portno] = &sparx5_dcb_apptrust_policies[idx];
|
|
|
|
return sparx5_dcb_app_update(dev);
|
|
}
|
|
|
|
static int sparx5_dcb_getapptrust(struct net_device *dev, u8 *selectors,
|
|
int *nselectors)
|
|
{
|
|
struct sparx5_port *port = netdev_priv(dev);
|
|
const struct sparx5_dcb_apptrust *trust;
|
|
|
|
trust = sparx5_port_apptrust[port->portno];
|
|
|
|
memcpy(selectors, trust->selectors, trust->nselectors);
|
|
*nselectors = trust->nselectors;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sparx5_dcb_delrewr(struct net_device *dev, struct dcb_app *app)
|
|
{
|
|
int err;
|
|
|
|
if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
|
|
err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_delrewr);
|
|
else
|
|
err = dcb_delrewr(dev, app);
|
|
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return sparx5_dcb_app_update(dev);
|
|
}
|
|
|
|
static int sparx5_dcb_setrewr(struct net_device *dev, struct dcb_app *app)
|
|
{
|
|
struct dcb_app app_itr;
|
|
int err = 0;
|
|
u16 proto;
|
|
|
|
err = sparx5_dcb_app_validate(dev, app);
|
|
if (err)
|
|
goto out;
|
|
|
|
/* Delete current mapping, if it exists. */
|
|
proto = dcb_getrewr(dev, app);
|
|
if (proto) {
|
|
app_itr = *app;
|
|
app_itr.protocol = proto;
|
|
sparx5_dcb_delrewr(dev, &app_itr);
|
|
}
|
|
|
|
if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
|
|
err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_setrewr);
|
|
else
|
|
err = dcb_setrewr(dev, app);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
sparx5_dcb_app_update(dev);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
const struct dcbnl_rtnl_ops sparx5_dcbnl_ops = {
|
|
.ieee_setapp = sparx5_dcb_ieee_setapp,
|
|
.ieee_delapp = sparx5_dcb_ieee_delapp,
|
|
.dcbnl_setapptrust = sparx5_dcb_setapptrust,
|
|
.dcbnl_getapptrust = sparx5_dcb_getapptrust,
|
|
.dcbnl_setrewr = sparx5_dcb_setrewr,
|
|
.dcbnl_delrewr = sparx5_dcb_delrewr,
|
|
};
|
|
|
|
int sparx5_dcb_init(struct sparx5 *sparx5)
|
|
{
|
|
struct sparx5_port *port;
|
|
int i;
|
|
|
|
for (i = 0; i < SPX5_PORTS; i++) {
|
|
port = sparx5->ports[i];
|
|
if (!port)
|
|
continue;
|
|
port->ndev->dcbnl_ops = &sparx5_dcbnl_ops;
|
|
/* Initialize [dscp, pcp] default trust */
|
|
sparx5_port_apptrust[port->portno] =
|
|
&sparx5_dcb_apptrust_policies
|
|
[SPARX5_DCB_APPTRUST_DSCP_PCP];
|
|
|
|
/* Enable DSCP classification based on classified QoS class and
|
|
* DP, for all DSCP values, for all ports.
|
|
*/
|
|
sparx5_port_qos_dscp_rewr_mode_set(port,
|
|
SPARX5_PORT_REW_DSCP_ALL);
|
|
}
|
|
|
|
return 0;
|
|
}
|