Merge branch 'dsa-felix-qos'
Vladimir Oltean says: ==================== Basic QoS classification on Felix DSA switch using dcbnl Basic QoS classification for Ocelot switches means port-based default priority, DSCP-based and VLAN PCP based. This is opposed to advanced QoS classification which is done through the VCAP IS1 TCAM based engine. The patch set is a logical continuation of this RFC which attempted to describe the default-prio as a matchall entry placed at the end of a series of offloaded tc filters: https://patchwork.kernel.org/project/netdevbpf/cover/20210113154139.1803705-1-olteanv@gmail.com/ I have tried my best to satisfy the feedback that we should cater for pre-configured QoS profiles. Ironically, the only pre-configured QoS profile that the Felix switch driver has is for VLAN PCP (1:1 mapping with QoS class), yet IEEE 802.1Q or dcbnl offer no mechanism for reporting or changing that. Testing was done with the iproute2 dcb app. The qos_class of packets was dumped from net/dsa/tag_ocelot.c. (1) $ dcb app show dev swp3 default-prio 0 (2) $ dcb app replace dev swp3 default-prio 3 (3) $ dcb app replace dev swp3 dscp-prio CS3:5 (4) $ dcb app replace dev swp3 dscp-prio CS2:2 (5) $ dcb app show dev swp3 default-prio 3 dscp-prio CS2:2 CS3:5 Traffic sent with "ping -Q 64 <ipaddr>", which means CS2. These packets match qos_class 0 after command (1), qos_class 3 after command (2), qos_class 3 after command (3), and qos_class 2 after command (2). ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
92ebb2361e
|
@ -1799,6 +1799,44 @@ felix_mrp_del_ring_role(struct dsa_switch *ds, int port,
|
|||
return ocelot_mrp_del_ring_role(ocelot, port, mrp);
|
||||
}
|
||||
|
||||
static int felix_port_get_default_prio(struct dsa_switch *ds, int port)
|
||||
{
|
||||
struct ocelot *ocelot = ds->priv;
|
||||
|
||||
return ocelot_port_get_default_prio(ocelot, port);
|
||||
}
|
||||
|
||||
static int felix_port_set_default_prio(struct dsa_switch *ds, int port,
|
||||
u8 prio)
|
||||
{
|
||||
struct ocelot *ocelot = ds->priv;
|
||||
|
||||
return ocelot_port_set_default_prio(ocelot, port, prio);
|
||||
}
|
||||
|
||||
static int felix_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp)
|
||||
{
|
||||
struct ocelot *ocelot = ds->priv;
|
||||
|
||||
return ocelot_port_get_dscp_prio(ocelot, port, dscp);
|
||||
}
|
||||
|
||||
static int felix_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
|
||||
u8 prio)
|
||||
{
|
||||
struct ocelot *ocelot = ds->priv;
|
||||
|
||||
return ocelot_port_add_dscp_prio(ocelot, port, dscp, prio);
|
||||
}
|
||||
|
||||
static int felix_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
|
||||
u8 prio)
|
||||
{
|
||||
struct ocelot *ocelot = ds->priv;
|
||||
|
||||
return ocelot_port_del_dscp_prio(ocelot, port, dscp, prio);
|
||||
}
|
||||
|
||||
const struct dsa_switch_ops felix_switch_ops = {
|
||||
.get_tag_protocol = felix_get_tag_protocol,
|
||||
.change_tag_protocol = felix_change_tag_protocol,
|
||||
|
@ -1862,6 +1900,11 @@ const struct dsa_switch_ops felix_switch_ops = {
|
|||
.port_mrp_del_ring_role = felix_mrp_del_ring_role,
|
||||
.tag_8021q_vlan_add = felix_tag_8021q_vlan_add,
|
||||
.tag_8021q_vlan_del = felix_tag_8021q_vlan_del,
|
||||
.port_get_default_prio = felix_port_get_default_prio,
|
||||
.port_set_default_prio = felix_port_set_default_prio,
|
||||
.port_get_dscp_prio = felix_port_get_dscp_prio,
|
||||
.port_add_dscp_prio = felix_port_add_dscp_prio,
|
||||
.port_del_dscp_prio = felix_port_del_dscp_prio,
|
||||
};
|
||||
|
||||
struct net_device *felix_port_to_netdev(struct ocelot *ocelot, int port)
|
||||
|
|
|
@ -2907,6 +2907,122 @@ void ocelot_port_bridge_flags(struct ocelot *ocelot, int port,
|
|||
}
|
||||
EXPORT_SYMBOL(ocelot_port_bridge_flags);
|
||||
|
||||
int ocelot_port_get_default_prio(struct ocelot *ocelot, int port)
|
||||
{
|
||||
int val = ocelot_read_gix(ocelot, ANA_PORT_QOS_CFG, port);
|
||||
|
||||
return ANA_PORT_QOS_CFG_QOS_DEFAULT_VAL_X(val);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ocelot_port_get_default_prio);
|
||||
|
||||
int ocelot_port_set_default_prio(struct ocelot *ocelot, int port, u8 prio)
|
||||
{
|
||||
if (prio >= IEEE_8021QAZ_MAX_TCS)
|
||||
return -ERANGE;
|
||||
|
||||
ocelot_rmw_gix(ocelot,
|
||||
ANA_PORT_QOS_CFG_QOS_DEFAULT_VAL(prio),
|
||||
ANA_PORT_QOS_CFG_QOS_DEFAULT_VAL_M,
|
||||
ANA_PORT_QOS_CFG,
|
||||
port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ocelot_port_set_default_prio);
|
||||
|
||||
int ocelot_port_get_dscp_prio(struct ocelot *ocelot, int port, u8 dscp)
|
||||
{
|
||||
int qos_cfg = ocelot_read_gix(ocelot, ANA_PORT_QOS_CFG, port);
|
||||
int dscp_cfg = ocelot_read_rix(ocelot, ANA_DSCP_CFG, dscp);
|
||||
|
||||
/* Return error if DSCP prioritization isn't enabled */
|
||||
if (!(qos_cfg & ANA_PORT_QOS_CFG_QOS_DSCP_ENA))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (qos_cfg & ANA_PORT_QOS_CFG_DSCP_TRANSLATE_ENA) {
|
||||
dscp = ANA_DSCP_CFG_DSCP_TRANSLATE_VAL_X(dscp_cfg);
|
||||
/* Re-read ANA_DSCP_CFG for the translated DSCP */
|
||||
dscp_cfg = ocelot_read_rix(ocelot, ANA_DSCP_CFG, dscp);
|
||||
}
|
||||
|
||||
/* If the DSCP value is not trusted, the QoS classification falls back
|
||||
* to VLAN PCP or port-based default.
|
||||
*/
|
||||
if (!(dscp_cfg & ANA_DSCP_CFG_DSCP_TRUST_ENA))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return ANA_DSCP_CFG_QOS_DSCP_VAL_X(dscp_cfg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ocelot_port_get_dscp_prio);
|
||||
|
||||
int ocelot_port_add_dscp_prio(struct ocelot *ocelot, int port, u8 dscp, u8 prio)
|
||||
{
|
||||
int mask, val;
|
||||
|
||||
if (prio >= IEEE_8021QAZ_MAX_TCS)
|
||||
return -ERANGE;
|
||||
|
||||
/* There is at least one app table priority (this one), so we need to
|
||||
* make sure DSCP prioritization is enabled on the port.
|
||||
* Also make sure DSCP translation is disabled
|
||||
* (dcbnl doesn't support it).
|
||||
*/
|
||||
mask = ANA_PORT_QOS_CFG_QOS_DSCP_ENA |
|
||||
ANA_PORT_QOS_CFG_DSCP_TRANSLATE_ENA;
|
||||
|
||||
ocelot_rmw_gix(ocelot, ANA_PORT_QOS_CFG_QOS_DSCP_ENA, mask,
|
||||
ANA_PORT_QOS_CFG, port);
|
||||
|
||||
/* Trust this DSCP value and map it to the given QoS class */
|
||||
val = ANA_DSCP_CFG_DSCP_TRUST_ENA | ANA_DSCP_CFG_QOS_DSCP_VAL(prio);
|
||||
|
||||
ocelot_write_rix(ocelot, val, ANA_DSCP_CFG, dscp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ocelot_port_add_dscp_prio);
|
||||
|
||||
int ocelot_port_del_dscp_prio(struct ocelot *ocelot, int port, u8 dscp, u8 prio)
|
||||
{
|
||||
int dscp_cfg = ocelot_read_rix(ocelot, ANA_DSCP_CFG, dscp);
|
||||
int mask, i;
|
||||
|
||||
/* During a "dcb app replace" command, the new app table entry will be
|
||||
* added first, then the old one will be deleted. But the hardware only
|
||||
* supports one QoS class per DSCP value (duh), so if we blindly delete
|
||||
* the app table entry for this DSCP value, we end up deleting the
|
||||
* entry with the new priority. Avoid that by checking whether user
|
||||
* space wants to delete the priority which is currently configured, or
|
||||
* something else which is no longer current.
|
||||
*/
|
||||
if (ANA_DSCP_CFG_QOS_DSCP_VAL_X(dscp_cfg) != prio)
|
||||
return 0;
|
||||
|
||||
/* Untrust this DSCP value */
|
||||
ocelot_write_rix(ocelot, 0, ANA_DSCP_CFG, dscp);
|
||||
|
||||
for (i = 0; i < 64; i++) {
|
||||
int dscp_cfg = ocelot_read_rix(ocelot, ANA_DSCP_CFG, i);
|
||||
|
||||
/* There are still app table entries on the port, so we need to
|
||||
* keep DSCP enabled, nothing to do.
|
||||
*/
|
||||
if (dscp_cfg & ANA_DSCP_CFG_DSCP_TRUST_ENA)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Disable DSCP QoS classification if there isn't any trusted
|
||||
* DSCP value left.
|
||||
*/
|
||||
mask = ANA_PORT_QOS_CFG_QOS_DSCP_ENA |
|
||||
ANA_PORT_QOS_CFG_DSCP_TRANSLATE_ENA;
|
||||
|
||||
ocelot_rmw_gix(ocelot, 0, mask, ANA_PORT_QOS_CFG, port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ocelot_port_del_dscp_prio);
|
||||
|
||||
void ocelot_init_port(struct ocelot *ocelot, int port)
|
||||
{
|
||||
struct ocelot_port *ocelot_port = ocelot->ports[port];
|
||||
|
|
|
@ -892,6 +892,18 @@ struct dsa_switch_ops {
|
|||
int (*get_ts_info)(struct dsa_switch *ds, int port,
|
||||
struct ethtool_ts_info *ts);
|
||||
|
||||
/*
|
||||
* DCB ops
|
||||
*/
|
||||
int (*port_get_default_prio)(struct dsa_switch *ds, int port);
|
||||
int (*port_set_default_prio)(struct dsa_switch *ds, int port,
|
||||
u8 prio);
|
||||
int (*port_get_dscp_prio)(struct dsa_switch *ds, int port, u8 dscp);
|
||||
int (*port_add_dscp_prio)(struct dsa_switch *ds, int port, u8 dscp,
|
||||
u8 prio);
|
||||
int (*port_del_dscp_prio)(struct dsa_switch *ds, int port, u8 dscp,
|
||||
u8 prio);
|
||||
|
||||
/*
|
||||
* Suspend and resume
|
||||
*/
|
||||
|
|
|
@ -869,6 +869,11 @@ int ocelot_port_pre_bridge_flags(struct ocelot *ocelot, int port,
|
|||
struct switchdev_brport_flags val);
|
||||
void ocelot_port_bridge_flags(struct ocelot *ocelot, int port,
|
||||
struct switchdev_brport_flags val);
|
||||
int ocelot_port_get_default_prio(struct ocelot *ocelot, int port);
|
||||
int ocelot_port_set_default_prio(struct ocelot *ocelot, int port, u8 prio);
|
||||
int ocelot_port_get_dscp_prio(struct ocelot *ocelot, int port, u8 dscp);
|
||||
int ocelot_port_add_dscp_prio(struct ocelot *ocelot, int port, u8 dscp, u8 prio);
|
||||
int ocelot_port_del_dscp_prio(struct ocelot *ocelot, int port, u8 dscp, u8 prio);
|
||||
int ocelot_port_bridge_join(struct ocelot *ocelot, int port,
|
||||
struct net_device *bridge, int bridge_num,
|
||||
struct netlink_ext_ack *extack);
|
||||
|
|
223
net/dsa/slave.c
223
net/dsa/slave.c
|
@ -19,6 +19,7 @@
|
|||
#include <net/tc_act/tc_mirred.h>
|
||||
#include <linux/if_bridge.h>
|
||||
#include <linux/if_hsr.h>
|
||||
#include <net/dcbnl.h>
|
||||
#include <linux/netpoll.h>
|
||||
|
||||
#include "dsa_priv.h"
|
||||
|
@ -1852,6 +1853,209 @@ out_master_failed:
|
|||
return err;
|
||||
}
|
||||
|
||||
static int __maybe_unused
|
||||
dsa_slave_dcbnl_set_default_prio(struct net_device *dev, struct dcb_app *app)
|
||||
{
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct dsa_switch *ds = dp->ds;
|
||||
unsigned long mask, new_prio;
|
||||
int err, port = dp->index;
|
||||
|
||||
if (!ds->ops->port_set_default_prio)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
err = dcb_ieee_setapp(dev, app);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mask = dcb_ieee_getapp_mask(dev, app);
|
||||
new_prio = __fls(mask);
|
||||
|
||||
err = ds->ops->port_set_default_prio(ds, port, new_prio);
|
||||
if (err) {
|
||||
dcb_ieee_delapp(dev, app);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused
|
||||
dsa_slave_dcbnl_add_dscp_prio(struct net_device *dev, struct dcb_app *app)
|
||||
{
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct dsa_switch *ds = dp->ds;
|
||||
unsigned long mask, new_prio;
|
||||
int err, port = dp->index;
|
||||
u8 dscp = app->protocol;
|
||||
|
||||
if (!ds->ops->port_add_dscp_prio)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (dscp >= 64) {
|
||||
netdev_err(dev, "DSCP APP entry with protocol value %u is invalid\n",
|
||||
dscp);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = dcb_ieee_setapp(dev, app);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mask = dcb_ieee_getapp_mask(dev, app);
|
||||
new_prio = __fls(mask);
|
||||
|
||||
err = ds->ops->port_add_dscp_prio(ds, port, dscp, new_prio);
|
||||
if (err) {
|
||||
dcb_ieee_delapp(dev, app);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused dsa_slave_dcbnl_ieee_setapp(struct net_device *dev,
|
||||
struct dcb_app *app)
|
||||
{
|
||||
switch (app->selector) {
|
||||
case IEEE_8021QAZ_APP_SEL_ETHERTYPE:
|
||||
switch (app->protocol) {
|
||||
case 0:
|
||||
return dsa_slave_dcbnl_set_default_prio(dev, app);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
case IEEE_8021QAZ_APP_SEL_DSCP:
|
||||
return dsa_slave_dcbnl_add_dscp_prio(dev, app);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int __maybe_unused
|
||||
dsa_slave_dcbnl_del_default_prio(struct net_device *dev, struct dcb_app *app)
|
||||
{
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct dsa_switch *ds = dp->ds;
|
||||
unsigned long mask, new_prio;
|
||||
int err, port = dp->index;
|
||||
|
||||
if (!ds->ops->port_set_default_prio)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
err = dcb_ieee_delapp(dev, app);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mask = dcb_ieee_getapp_mask(dev, app);
|
||||
new_prio = mask ? __fls(mask) : 0;
|
||||
|
||||
err = ds->ops->port_set_default_prio(ds, port, new_prio);
|
||||
if (err) {
|
||||
dcb_ieee_setapp(dev, app);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused
|
||||
dsa_slave_dcbnl_del_dscp_prio(struct net_device *dev, struct dcb_app *app)
|
||||
{
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct dsa_switch *ds = dp->ds;
|
||||
int err, port = dp->index;
|
||||
u8 dscp = app->protocol;
|
||||
|
||||
if (!ds->ops->port_del_dscp_prio)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
err = dcb_ieee_delapp(dev, app);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = ds->ops->port_del_dscp_prio(ds, port, dscp, app->priority);
|
||||
if (err) {
|
||||
dcb_ieee_setapp(dev, app);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused dsa_slave_dcbnl_ieee_delapp(struct net_device *dev,
|
||||
struct dcb_app *app)
|
||||
{
|
||||
switch (app->selector) {
|
||||
case IEEE_8021QAZ_APP_SEL_ETHERTYPE:
|
||||
switch (app->protocol) {
|
||||
case 0:
|
||||
return dsa_slave_dcbnl_del_default_prio(dev, app);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
case IEEE_8021QAZ_APP_SEL_DSCP:
|
||||
return dsa_slave_dcbnl_del_dscp_prio(dev, app);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
/* Pre-populate the DCB application priority table with the priorities
|
||||
* configured during switch setup, which we read from hardware here.
|
||||
*/
|
||||
static int dsa_slave_dcbnl_init(struct net_device *dev)
|
||||
{
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct dsa_switch *ds = dp->ds;
|
||||
int port = dp->index;
|
||||
int err;
|
||||
|
||||
if (ds->ops->port_get_default_prio) {
|
||||
int prio = ds->ops->port_get_default_prio(ds, port);
|
||||
struct dcb_app app = {
|
||||
.selector = IEEE_8021QAZ_APP_SEL_ETHERTYPE,
|
||||
.protocol = 0,
|
||||
.priority = prio,
|
||||
};
|
||||
|
||||
if (prio < 0)
|
||||
return prio;
|
||||
|
||||
err = dcb_ieee_setapp(dev, &app);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ds->ops->port_get_dscp_prio) {
|
||||
int protocol;
|
||||
|
||||
for (protocol = 0; protocol < 64; protocol++) {
|
||||
struct dcb_app app = {
|
||||
.selector = IEEE_8021QAZ_APP_SEL_DSCP,
|
||||
.protocol = protocol,
|
||||
};
|
||||
int prio;
|
||||
|
||||
prio = ds->ops->port_get_dscp_prio(ds, port, protocol);
|
||||
if (prio == -EOPNOTSUPP)
|
||||
continue;
|
||||
if (prio < 0)
|
||||
return prio;
|
||||
|
||||
app.priority = prio;
|
||||
|
||||
err = dcb_ieee_setapp(dev, &app);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ethtool_ops dsa_slave_ethtool_ops = {
|
||||
.get_drvinfo = dsa_slave_get_drvinfo,
|
||||
.get_regs_len = dsa_slave_get_regs_len,
|
||||
|
@ -1881,6 +2085,11 @@ static const struct ethtool_ops dsa_slave_ethtool_ops = {
|
|||
.self_test = dsa_slave_net_selftest,
|
||||
};
|
||||
|
||||
static const struct dcbnl_rtnl_ops __maybe_unused dsa_slave_dcbnl_ops = {
|
||||
.ieee_setapp = dsa_slave_dcbnl_ieee_setapp,
|
||||
.ieee_delapp = dsa_slave_dcbnl_ieee_delapp,
|
||||
};
|
||||
|
||||
static struct devlink_port *dsa_slave_get_devlink_port(struct net_device *dev)
|
||||
{
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
|
@ -2105,6 +2314,9 @@ int dsa_slave_create(struct dsa_port *port)
|
|||
return -ENOMEM;
|
||||
|
||||
slave_dev->ethtool_ops = &dsa_slave_ethtool_ops;
|
||||
#if IS_ENABLED(CONFIG_DCB)
|
||||
slave_dev->dcbnl_ops = &dsa_slave_dcbnl_ops;
|
||||
#endif
|
||||
if (!is_zero_ether_addr(port->mac))
|
||||
eth_hw_addr_set(slave_dev, port->mac);
|
||||
else
|
||||
|
@ -2162,6 +2374,17 @@ int dsa_slave_create(struct dsa_port *port)
|
|||
goto out_phy;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_DCB)) {
|
||||
ret = dsa_slave_dcbnl_init(slave_dev);
|
||||
if (ret) {
|
||||
netdev_err(slave_dev,
|
||||
"failed to initialize DCB: %pe\n",
|
||||
ERR_PTR(ret));
|
||||
rtnl_unlock();
|
||||
goto out_unregister;
|
||||
}
|
||||
}
|
||||
|
||||
ret = netdev_upper_dev_link(master, slave_dev, NULL);
|
||||
|
||||
rtnl_unlock();
|
||||
|
|
Loading…
Reference in New Issue